395 lines
8.7 KiB
Markdown
395 lines
8.7 KiB
Markdown
# Container Deployment Guide
|
|
|
|
This guide covers the containerized deployment setup for the www.ermnvldmr.com website, including both the main Astro site and the Storybook component library.
|
|
|
|
## Overview
|
|
|
|
The project uses Docker containers to package and deploy:
|
|
- **Main Site**: Astro static website served via nginx
|
|
- **Storybook**: Component library served via nginx
|
|
- **Registry**: GitHub Container Registry (ghcr.io)
|
|
- **CI/CD**: GitHub Actions with semantic versioning
|
|
|
|
## Container Architecture
|
|
|
|
### Multi-stage Build Strategy
|
|
|
|
Both containers follow a multi-stage build pattern:
|
|
|
|
1. **Builder Stage**: Node.js 20 Alpine with pnpm for building assets
|
|
2. **Production Stage**: nginx:1.25-alpine for serving static content
|
|
|
|
### Security Features
|
|
|
|
- **Non-root user**: Containers run as `nginx-user` (UID 1001)
|
|
- **Non-privileged ports**: Expose port 8080 instead of 80
|
|
- **Security headers**: CSP, HSTS, and other security headers configured
|
|
- **Signal handling**: dumb-init for proper signal handling
|
|
|
|
### Images
|
|
|
|
- **Main Site**: `ghcr.io/deytenit/www.ermnvldmr.com`
|
|
- **Storybook**: `ghcr.io/deytenit/www.ermnvldmr.com/storybook`
|
|
|
|
## Tagging Strategy
|
|
|
|
### Release Tags (from `release/semantic-dispatch`)
|
|
|
|
For both images:
|
|
- `vX.X.X` - Full version (e.g., `v1.2.3`)
|
|
- `vX.X` - Minor version (e.g., `v1.2`)
|
|
- `vX` - Major version (e.g., `v1`)
|
|
- `latest` - Latest stable release
|
|
- `{sha}` - Commit SHA for traceability
|
|
|
|
### Pre-release Tags (from `next/semantic-dispatch`)
|
|
|
|
For both images:
|
|
- `vX.X.X-next.X` - Full prerelease version (e.g., `v1.2.3-next.1`)
|
|
- `vX.X.X-next` - Latest prerelease for that version
|
|
- `vX.X-next` - Latest prerelease for minor version
|
|
- `vX-next` - Latest prerelease for major version
|
|
- `latest-next` - Latest prerelease overall
|
|
- `{sha}` - Commit SHA for traceability
|
|
|
|
## Local Development
|
|
|
|
### Using Docker Compose
|
|
|
|
Start both services locally:
|
|
|
|
```bash
|
|
# Start both containers
|
|
docker-compose up
|
|
|
|
# Start in background
|
|
docker-compose up -d
|
|
|
|
# View logs
|
|
docker-compose logs -f
|
|
|
|
# Stop services
|
|
docker-compose down
|
|
```
|
|
|
|
Access points:
|
|
- **Main Site**: http://localhost:3000
|
|
- **Storybook**: http://localhost:6006
|
|
|
|
### Building Individual Images
|
|
|
|
```bash
|
|
# Build Astro site
|
|
docker build -f docker/astro/Dockerfile -t www-astro .
|
|
|
|
# Build Storybook
|
|
docker build -f docker/storybook/Dockerfile -t www-storybook .
|
|
|
|
# Run containers
|
|
docker run -p 3000:8080 www-astro
|
|
docker run -p 6006:8080 www-storybook
|
|
```
|
|
|
|
## Production Deployment
|
|
|
|
### Manual Container Deployment
|
|
|
|
Pull and run the latest images:
|
|
|
|
```bash
|
|
# Pull latest stable images
|
|
docker pull ghcr.io/deytenit/www.ermnvldmr.com:latest
|
|
docker pull ghcr.io/deytenit/www.ermnvldmr.com/storybook:latest
|
|
|
|
# Run main site
|
|
docker run -d \
|
|
--name www-ermnvldmr-astro \
|
|
-p 3000:8080 \
|
|
--restart unless-stopped \
|
|
ghcr.io/deytenit/www.ermnvldmr.com:latest
|
|
|
|
# Run storybook
|
|
docker run -d \
|
|
--name www-ermnvldmr-storybook \
|
|
-p 6006:8080 \
|
|
--restart unless-stopped \
|
|
ghcr.io/deytenit/www.ermnvldmr.com/storybook:latest
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
The containers are designed to be stateless and don't require runtime environment variables. Configuration is baked into the build.
|
|
|
|
### Health Checks
|
|
|
|
Both containers include health check endpoints:
|
|
|
|
```bash
|
|
# Check container health
|
|
curl http://localhost:3000/health # Astro site
|
|
curl http://localhost:6006/health # Storybook
|
|
```
|
|
|
|
### Resource Requirements
|
|
|
|
**Recommended minimums per container:**
|
|
- **CPU**: 0.1 cores
|
|
- **Memory**: 64MB
|
|
- **Storage**: 100MB
|
|
|
|
**Production recommendations:**
|
|
- **CPU**: 0.25 cores
|
|
- **Memory**: 128MB
|
|
- **Storage**: 500MB (for logs and cache)
|
|
|
|
## Reverse Proxy Configuration
|
|
|
|
### nginx Configuration Example
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name www.ermnvldmr.com;
|
|
|
|
location / {
|
|
proxy_pass http://astro-container:8080;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name storybook.ermnvldmr.com;
|
|
|
|
location / {
|
|
proxy_pass http://storybook-container:8080;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Traefik Configuration Example
|
|
|
|
```yaml
|
|
# docker-compose.yml with Traefik
|
|
version: '3.8'
|
|
|
|
services:
|
|
astro:
|
|
image: ghcr.io/deytenit/www.ermnvldmr.com:latest
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.astro.rule=Host(`www.ermnvldmr.com`)"
|
|
- "traefik.http.services.astro.loadbalancer.server.port=8080"
|
|
|
|
storybook:
|
|
image: ghcr.io/deytenit/www.ermnvldmr.com/storybook:latest
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.storybook.rule=Host(`storybook.ermnvldmr.com`)"
|
|
- "traefik.http.services.storybook.loadbalancer.server.port=8080"
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
#### Container Won't Start
|
|
|
|
```bash
|
|
# Check logs
|
|
docker logs container-name
|
|
|
|
# Common causes:
|
|
# - Missing dependencies in build
|
|
# - Permission issues
|
|
# - Port conflicts
|
|
```
|
|
|
|
#### Build Failures
|
|
|
|
```bash
|
|
# Check build logs
|
|
docker build -f docker/astro/Dockerfile -t test .
|
|
|
|
# Common causes:
|
|
# - Node version mismatch
|
|
# - pnpm cache issues
|
|
# - Missing build dependencies
|
|
```
|
|
|
|
#### Permission Denied
|
|
|
|
```bash
|
|
# Check if running as correct user
|
|
docker exec container-name whoami
|
|
|
|
# Should output: nginx-user
|
|
```
|
|
|
|
### Debugging
|
|
|
|
Enable debug mode in nginx:
|
|
|
|
```bash
|
|
# Create debug container
|
|
docker run -it --entrypoint=/bin/sh ghcr.io/deytenit/www.ermnvldmr.com:latest
|
|
|
|
# Check nginx config
|
|
nginx -t
|
|
|
|
# Check file permissions
|
|
ls -la /usr/share/nginx/html/
|
|
```
|
|
|
|
### Performance Tuning
|
|
|
|
#### nginx Optimization
|
|
|
|
For high-traffic deployments, consider:
|
|
|
|
- Adjusting worker processes
|
|
- Enabling gzip compression levels
|
|
- Tuning keepalive settings
|
|
- Adding rate limiting
|
|
|
|
#### Container Resources
|
|
|
|
Monitor and adjust:
|
|
|
|
```bash
|
|
# Check resource usage
|
|
docker stats
|
|
|
|
# Update container limits
|
|
docker run --memory=256m --cpus=0.5 ...
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
### Container Security
|
|
|
|
- Containers run as non-root user
|
|
- Minimal attack surface with Alpine base
|
|
- Regular security updates via base image updates
|
|
- No sensitive data in containers
|
|
|
|
### Network Security
|
|
|
|
- Expose only necessary ports
|
|
- Use internal networks for service communication
|
|
- Implement proper firewall rules
|
|
- Enable HTTPS with reverse proxy
|
|
|
|
### Registry Security
|
|
|
|
- Images are signed and scanned
|
|
- Access controlled via GitHub permissions
|
|
- Automatic vulnerability scanning
|
|
- Regular base image updates
|
|
|
|
## Monitoring
|
|
|
|
### Health Monitoring
|
|
|
|
```bash
|
|
# Simple health check script
|
|
#!/bin/bash
|
|
if curl -f http://localhost:3000/health >/dev/null 2>&1; then
|
|
echo "Astro site: healthy"
|
|
else
|
|
echo "Astro site: unhealthy"
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
### Log Management
|
|
|
|
```bash
|
|
# View logs
|
|
docker-compose logs -f astro
|
|
docker-compose logs -f storybook
|
|
|
|
# Log rotation (configure docker daemon)
|
|
# /etc/docker/daemon.json
|
|
{
|
|
"log-driver": "json-file",
|
|
"log-opts": {
|
|
"max-size": "10m",
|
|
"max-file": "3"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Metrics Collection
|
|
|
|
Consider integrating with:
|
|
- Prometheus + Grafana
|
|
- nginx prometheus exporter
|
|
- Container metrics exporters
|
|
- Application performance monitoring
|
|
|
|
## CI/CD Integration
|
|
|
|
The containers are automatically built and pushed when:
|
|
|
|
1. **Pre-release**: Merge to `next` branch triggers prerelease
|
|
2. **Release**: Manual workflow dispatch creates stable release
|
|
|
|
### Workflow Outputs
|
|
|
|
Each workflow provides:
|
|
- `new_release_published`: Boolean indicating if release was created
|
|
- `new_release_version`: The version number of the release
|
|
|
|
### Manual Triggers
|
|
|
|
```bash
|
|
# Trigger release workflow via GitHub CLI
|
|
gh workflow run "release/semantic-dispatch" --ref main
|
|
|
|
# Trigger prerelease via merge to next branch
|
|
git checkout next
|
|
git merge feature-branch
|
|
git push origin next
|
|
```
|
|
|
|
## Maintenance
|
|
|
|
### Regular Updates
|
|
|
|
1. **Base Image Updates**: Rebuild monthly for security patches
|
|
2. **Dependency Updates**: Follow project dependency update schedule
|
|
3. **Configuration Review**: Quarterly review of nginx and security settings
|
|
|
|
### Backup Strategy
|
|
|
|
Since containers are stateless:
|
|
- Source code is backed up in Git repository
|
|
- Container images are versioned in registry
|
|
- No persistent data to backup
|
|
|
|
### Rollback Procedure
|
|
|
|
```bash
|
|
# Rollback to previous version
|
|
docker pull ghcr.io/deytenit/www.ermnvldmr.com:v1.2.2
|
|
docker stop current-container
|
|
docker run -d --name new-container ghcr.io/deytenit/www.ermnvldmr.com:v1.2.2
|
|
|
|
# Or use docker-compose with specific version
|
|
docker-compose down
|
|
# Edit docker-compose.yml to specify version
|
|
docker-compose up -d
|
|
```
|
|
|
|
---
|
|
|
|
For additional support or questions, refer to the project repository issues or documentation.
|