🔥 0
0
Lesson 9 of 10 25 min +150 XP

E-commerce Stack with Compose

Running docker run for each service gets tedious. Docker Compose lets you define your entire stack in one file and manage it with simple commands.

The Problem with Multiple docker run Commands

Starting our e-commerce stack manually:

# Create network
docker network create shopflow-net

# Start database
docker run -d --name db --network shopflow-net \
  -e POSTGRES_USER=shopflow \
  -e POSTGRES_PASSWORD=secret \
  -v db-data:/var/lib/postgresql/data \
  postgres:15

# Start cache
docker run -d --name cache --network shopflow-net \
  -v cache-data:/data \
  redis:7-alpine

# Start product API
docker run -d --name product-api --network shopflow-net \
  -e DATABASE_URL=postgresql://shopflow:secret@db:5432/shopflow \
  -e REDIS_URL=redis://cache:6379 \
  -p 3001:3000 \
  shopflow/product-api:1.0

# Start payment service
docker run -d --name payment-service --network shopflow-net \
  -e DATABASE_URL=postgresql://shopflow:secret@db:5432/shopflow \
  -e STRIPE_SECRET_KEY=sk_test_xxx \
  -p 3002:3000 \
  shopflow/payment-service:1.0

# Start web frontend
docker run -d --name web --network shopflow-net \
  -p 80:80 \
  shopflow/web-store:1.0

That's 6 commands with lots of flags. Easy to make mistakes. Hard to version control.

Docker Compose Solution

Create docker-compose.yml:

version: '3.8'

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: shopflow
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: shopflow
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U shopflow"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - cache-data:/data

  product-api:
    build: ./services/product-api
    environment:
      DATABASE_URL: postgresql://shopflow:secret@db:5432/shopflow
      REDIS_URL: redis://cache:6379
      PORT: 3000
    ports:
      - "3001:3000"
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  payment-service:
    build: ./services/payment-service
    environment:
      DATABASE_URL: postgresql://shopflow:secret@db:5432/shopflow
      STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
    ports:
      - "3002:3000"
    depends_on:
      - db

  web:
    build: ./services/web-store
    ports:
      - "80:80"
    depends_on:
      - product-api
      - payment-service

volumes:
  db-data:
  cache-data:

Now start everything:

docker compose up -d

That's it. One command. All services running.

Project Structure

shopflow/
├── docker-compose.yml
├── docker-compose.override.yml    # Dev overrides (optional)
├── docker-compose.prod.yml        # Prod overrides (optional)
├── .env                           # Environment variables
├── services/
│   ├── product-api/
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   └── src/
│   ├── payment-service/
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   └── src/
│   └── web-store/
│       ├── Dockerfile
│       └── nginx.conf

Essential Compose Commands

# Start all services (detached)
docker compose up -d

# Start and rebuild images
docker compose up -d --build

# View running services
docker compose ps

# View logs
docker compose logs

# Follow logs for specific service
docker compose logs -f product-api

# Stop all services
docker compose stop

# Stop and remove containers, networks
docker compose down

# Stop and remove everything including volumes
docker compose down -v

# Restart a service
docker compose restart product-api

# Execute command in running service
docker compose exec db psql -U shopflow

# Run one-off command
docker compose run --rm product-api npm test

Complete E-commerce docker-compose.yml

Here's a production-ready setup:

version: '3.8'

services:
  # ===================
  # Database Layer
  # ===================
  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${DB_USER:-shopflow}
      POSTGRES_PASSWORD: ${DB_PASSWORD:?Database password required}
      POSTGRES_DB: ${DB_NAME:-shopflow}
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-shopflow}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  cache:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  # ===================
  # Application Layer
  # ===================
  product-api:
    build:
      context: ./services/product-api
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      NODE_ENV: production
      PORT: 3000
      DATABASE_URL: postgresql://${DB_USER:-shopflow}:${DB_PASSWORD}@db:5432/${DB_NAME:-shopflow}
      REDIS_URL: redis://cache:6379
    ports:
      - "3001:3000"
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - backend
      - frontend

  cart-service:
    build:
      context: ./services/cart-service
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      NODE_ENV: production
      PORT: 3000
      REDIS_URL: redis://cache:6379
      PRODUCT_API_URL: http://product-api:3000
    ports:
      - "3003:3000"
    depends_on:
      - cache
      - product-api
    networks:
      - backend
      - frontend

  payment-service:
    build:
      context: ./services/payment-service
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      NODE_ENV: production
      PORT: 3000
      DATABASE_URL: postgresql://${DB_USER:-shopflow}:${DB_PASSWORD}@db:5432/${DB_NAME:-shopflow}
      STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:?Stripe key required}
      STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
    ports:
      - "3002:3000"
    depends_on:
      db:
        condition: service_healthy
    networks:
      - backend
      - frontend

  # ===================
  # Frontend Layer
  # ===================
  web:
    build:
      context: ./services/web-store
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - product-images:/usr/share/nginx/html/images:ro
    depends_on:
      - product-api
      - cart-service
      - payment-service
    networks:
      - frontend

  # ===================
  # Background Workers
  # ===================
  order-processor:
    build:
      context: ./services/order-processor
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      DATABASE_URL: postgresql://${DB_USER:-shopflow}:${DB_PASSWORD}@db:5432/${DB_NAME:-shopflow}
      REDIS_URL: redis://cache:6379
      SENDGRID_API_KEY: ${SENDGRID_API_KEY}
    depends_on:
      - db
      - cache
    networks:
      - backend

# ===================
# Networks
# ===================
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

# ===================
# Volumes
# ===================
volumes:
  postgres-data:
  redis-data:
  product-images:

Environment Variables with .env

Create .env file:

# Database
DB_USER=shopflow
DB_PASSWORD=your-secure-password-here
DB_NAME=shopflow

# Stripe
STRIPE_SECRET_KEY=sk_test_your-key-here
STRIPE_WEBHOOK_SECRET=whsec_your-secret

# Email
SENDGRID_API_KEY=SG.your-key

Compose automatically loads .env from the same directory.

Development Overrides

Create docker-compose.override.yml for development (auto-loaded):

version: '3.8'

services:
  product-api:
    build:
      context: ./services/product-api
      target: development
    volumes:
      - ./services/product-api/src:/app/src
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
    command: npm run dev

  payment-service:
    volumes:
      - ./services/payment-service/src:/app/src
    environment:
      NODE_ENV: development
    command: npm run dev

  # Add debugging tools
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db

  redis-commander:
    image: rediscommander/redis-commander
    environment:
      REDIS_HOSTS: cache
    ports:
      - "8081:8081"
    depends_on:
      - cache

Development automatically includes hot-reload and debugging tools.

Production Configuration

Create docker-compose.prod.yml:

version: '3.8'

services:
  product-api:
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  db:
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 1G

Run with production config:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Useful Patterns

Wait for Database

product-api:
  depends_on:
    db:
      condition: service_healthy

Build Arguments

product-api:
  build:
    context: ./services/product-api
    args:
      NODE_VERSION: 20
      BUILD_DATE: ${BUILD_DATE:-unknown}

Shared Configuration

x-common-env: &common-env
  NODE_ENV: production
  LOG_LEVEL: info

services:
  product-api:
    environment:
      <<: *common-env
      PORT: 3000

  payment-service:
    environment:
      <<: *common-env
      PORT: 3001

Debugging

# See generated configuration
docker compose config

# See why a service won't start
docker compose logs product-api

# Get a shell in a service
docker compose exec product-api sh

# Check service health
docker compose ps

Key Takeaways

  • docker-compose.yml defines your entire stack declaratively
  • docker compose up -d starts everything
  • Services communicate using service names as hostnames
  • depends_on controls startup order
  • Use .env files for configuration
  • Override files for dev/prod differences
  • Volumes persist data across restarts

You now have a complete, production-ready e-commerce stack running with one command!

🧠 Quick Quiz

Test your understanding of this lesson.

1

What command starts all services defined in docker-compose.yml?

2

How do containers communicate in Docker Compose?

3

What does 'depends_on' do in a Compose file?

Configuring Payment & API Keys