Docker Compose for Production Web Deployments
DevOps

Docker Compose for Production Web Deployments

  • AuthorLiam K.
  • Date
  • Time20 minutes

Goal

Deploy an app, a PostgreSQL database, and Nginx reverse proxy using a reproducible Docker Compose setup.

Step 1: Install Docker and Compose Plugin

bash
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER

Step 2: Create Project Layout

bash
mkdir -p ~/prod-stack/{app,nginx,db,logs}
cd ~/prod-stack

Step 3: Add Environment File

bash
cat > .env <<'EOF'
APP_PORT=3000
POSTGRES_DB=appdb
POSTGRES_USER=appuser
POSTGRES_PASSWORD=replace_with_long_password
EOF

Step 4: Create docker-compose.yml

yaml
cat > docker-compose.yml <<'EOF'
services:
  app:
    image: node:20-alpine
    working_dir: /app
    command: sh -c "npm ci && npm run start"
    volumes:
      - ./app:/app
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 20s
      timeout: 5s
      retries: 5

  db:
    image: postgres:16-alpine
    env_file:
      - .env
    volumes:
      - pg_data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 10s
      timeout: 5s
      retries: 10

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      app:
        condition: service_healthy
    restart: unless-stopped

volumes:
  pg_data:
EOF

Step 5: Add Nginx Proxy Config

nginx
cat > nginx/default.conf <<'EOF'
server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://app:3000;
        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;
    }
}
EOF

Step 6: Start Stack and Validate

bash
docker compose pull
docker compose up -d
docker compose ps
docker compose logs -f --tail=100

Step 7: Safe Update Procedure

bash
git pull
docker compose build --pull
docker compose up -d --remove-orphans
docker image prune -f

Step 8: Backups for Persistent Data

bash
docker exec -t $(docker compose ps -q db) pg_dump -U appuser appdb > backup_$(date +%F).sql

"Compose is excellent for small and medium production systems if you treat it like infrastructure code and keep updates disciplined."

Technical Author

Liam K.

Liam K.

Founder of Trivox. Writes about how the platform, the network, and the Amsterdam sites fit together.