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!