Production Build Process
Production Build Process
Build Production Images
# Build frontend
docker build -t myregistry.com/taskmanager-frontend:1.0.0 ./frontend
# Build API
docker build -t myregistry.com/taskmanager-api:1.0.0 ./api
# Verify image sizes
docker images myregistry/*
# REPOSITORY TAG SIZE
# myregistry.com/taskmanager-frontend 1.0.0 25MB
# myregistry.com/taskmanager-api 1.0.0 120MB
Push to Registry
# Log in to registry
docker login myregistry.com
# Push images
docker push myregistry.com/taskmanager-frontend:1.0.0
docker push myregistry.com/taskmanager-api:1.0.0
# Push with git SHA tag
git rev-parse --short HEAD
# abc1234
docker tag myregistry.com/taskmanager-api:1.0.0 myregistry.com/taskmanager-api:abc1234
docker push myregistry.com/taskmanager-api:abc1234
Build Scripts
#!/bin/bash
# scripts/build.sh
set -e
REGISTRY="myregistry.com"
VERSION=$(git rev-parse --short HEAD)
echo "Building images for version: $VERSION"
# Build frontend
docker build -t ${REGISTRY}/taskmanager-frontend:${VERSION} \
-t ${REGISTRY}/taskmanager-frontend:latest \
./frontend
# Build API
docker build -t ${REGISTRY}/taskmanager-api:${VERSION} \
-t ${REGISTRY}/taskmanager-api:latest \
./api
# Push
docker push ${REGISTRY}/taskmanager-frontend:${VERSION}
docker push ${REGISTRY}/taskmanager-frontend:latest
docker push ${REGISTRY}/taskmanager-api:${VERSION}
docker push ${REGISTRY}/taskmanager-api:latest
echo "Build complete!"
CI/CD with GitHub Actions
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ secrets.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push frontend
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: ${{ secrets.REGISTRY }}/taskmanager-frontend:${{ github.sha }}
- name: Build and push API
uses: docker/build-push-action@v5
with:
context: ./api
push: true
tags: ${{ secrets.REGISTRY }}/taskmanager-api:${{ github.sha }}
- name: Deploy to production
run: |
ssh deploy@server "cd /opt/taskmanager && \
docker compose -f docker-compose.prod.yml pull && \
docker compose -f docker-compose.prod.yml up -d"
Docker Compose Build
# Build all services
docker compose build
# Build with no cache
docker compose build --no-cache
# Build and push
docker compose build && docker compose push
# Use build args
docker compose build --build-arg NODE_VERSION=20
Production Deployment
Production Deployment
Server Setup
# SSH into production server
ssh deploy@production-server
# Install Docker
curl -fsSL https://get.docker.com | sh
# Create project directory
sudo mkdir -p /opt/taskmanager
cd /opt/taskmanager
# Copy compose files
scp docker-compose.prod.yml deploy@production-server:/opt/taskmanager/
# Copy .env
scp .env deploy@production-server:/opt/taskmanager/
# Create .env file
cat > .env <<EOF
POSTGRES_DB=taskmanager
POSTGRES_USER=taskadmin
POSTGRES_PASSWORD=secure_password_here
DATABASE_URL=postgresql://taskadmin:secure_password_here@db:5432/taskmanager
REDIS_URL=redis://redis:6379
EOF
Deploy with Compose
# Pull and start services
cd /opt/taskmanager
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
# Check status
docker compose ps
# View logs
docker compose logs -f
# Verify health
curl http://localhost:4000/health
curl http://localhost:80
Rolling Updates
# docker-compose.prod.yml with rolling updates
services:
api:
deploy:
replicas: 3
update_config:
parallelism: 1 # Update one at a time
delay: 10s # Wait 10s between updates
failure_action: rollback
order: start-first # Start new before stopping old
rollback_config:
parallelism: 1
delay: 5s
# Update with rolling deployment
docker compose -f docker-compose.prod.yml up -d --no-deps api
# Scale API
docker compose -f docker-compose.prod.yml up -d --scale api=3
Database Migrations
# Run migrations before deploying new code
docker compose -f docker-compose.prod.yml exec api node migrate.js
# Or as a one-off container
docker compose -f docker-compose.prod.yml run --rm api node migrate.js
SSL/TLS with Let's Encrypt
services:
nginx:
image: nginx:1.25
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- certbot_data:/etc/letsencrypt
- certbot_www:/var/www/certbot
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
image: certbot/certbot
volumes:
- certbot_data:/etc/letsencrypt
- certbot_www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
volumes:
certbot_data:
certbot_www:
Backup Strategy
#!/bin/bash
# scripts/backup.sh
BACKUP_DIR="/backups/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
# Backup database
docker compose -f docker-compose.prod.yml exec -T db pg_dumpall -U taskadmin > $BACKUP_DIR/db.sql
# Backup volumes
docker run --rm \
-v taskmanager_pgdata:/source:ro \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/pgdata.tar.gz -C /source .
# Backup images
docker save myregistry.com/taskmanager-api:latest | gzip > $BACKUP_DIR/api.tar.gz
# Clean old backups (keep 7 days)
find /backups -type d -mtime +7 -exec rm -rf {} +
echo "Backup complete: $BACKUP_DIR"
Monitoring
# Check container health
docker compose ps
# View resource usage
docker stats --no-stream
# Check logs for errors
docker compose logs --tail 100 api | grep -i error
# Health check endpoint
curl -s http://localhost:4000/health | jq
Production Troubleshooting
Production Troubleshooting
Common Production Issues
1. Container won't start:
# Check container status
docker compose ps -a
# Check logs
docker compose logs api
# Common: Missing environment variable
docker compose exec api env | grep DATABASE_URL
# Common: Permission issues
docker compose exec api ls -la /app
2. Health check failing:
# Test health endpoint manually
docker compose exec api wget -qO- http://localhost:4000/health
# Check if service is listening
docker compose exec api ss -tlnp
# Check resource usage
docker stats api
3. Database connection refused:
# Check database is running
docker compose ps db
# Check database health
docker compose exec db pg_isready -U taskadmin
# Test connection from API
docker compose exec api pg_isready -h db -p 5432
# Check database logs
docker compose logs db | tail -20
4. Out of memory:
# Check OOM kills
docker inspect api | jq '.[0].State.OOMKilled'
# Increase memory limit
docker compose -f docker-compose.prod.yml up -d --no-deps api
# Monitor memory usage
docker stats api --no-stream
5. Disk space:
# Check disk usage
docker system df
# Clean unused resources
docker system prune -a --volumes
# Check log sizes
du -sh /var/lib/docker/containers/*/
Deployment Checklist
# Pre-deployment
□ Run tests locally
□ Build and test images
□ Update .env with production values
□ Run database migrations
□ Backup existing data
# Deployment
□ Pull new images
□ Stop old containers
□ Start new containers
□ Verify health checks
□ Run smoke tests
□ Monitor logs for errors
# Post-deployment
□ Verify application works
□ Check performance metrics
□ Update documentation
□ Notify team
Rollback Procedure
# If deployment fails, rollback to previous version
cd /opt/taskmanager
# Edit docker-compose.prod.yml to use previous image tag
# Then:
docker compose -f docker-compose.prod.yml up -d
# Or restore from backup
bash scripts/restore.sh <backup_date>
# Verify rollback
curl http://localhost:4000/health
docker compose logs --tail 50 api