Docker Patterns
Docker and Docker Compose best practices for containerized development.
When to Activate
- Setting up Docker Compose for local development
- Designing multi-container architectures
- Troubleshooting container networking or volume issues
- Reviewing Dockerfiles for security and size
- Migrating from local dev to containerized workflow
Docker Compose for Local Development
Standard Web App Stack
yaml1# docker-compose.yml 2services: 3 app: 4 build: 5 context: . 6 target: dev # Use dev stage of multi-stage Dockerfile 7 ports: 8 - "3000:3000" 9 volumes: 10 - .:/app # Bind mount for hot reload 11 - /app/node_modules # Anonymous volume -- preserves container deps 12 environment: 13 - DATABASE_URL=postgres://postgres:postgres@db:5432/app_dev 14 - REDIS_URL=redis://redis:6379/0 15 - NODE_ENV=development 16 depends_on: 17 db: 18 condition: service_healthy 19 redis: 20 condition: service_started 21 command: npm run dev 22 23 db: 24 image: postgres:16-alpine 25 ports: 26 - "5432:5432" 27 environment: 28 POSTGRES_USER: postgres 29 POSTGRES_PASSWORD: postgres 30 POSTGRES_DB: app_dev 31 volumes: 32 - pgdata:/var/lib/postgresql/data 33 - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql 34 healthcheck: 35 test: ["CMD-SHELL", "pg_isready -U postgres"] 36 interval: 5s 37 timeout: 3s 38 retries: 5 39 40 redis: 41 image: redis:7-alpine 42 ports: 43 - "6379:6379" 44 volumes: 45 - redisdata:/data 46 47 mailpit: # Local email testing 48 image: axllent/mailpit 49 ports: 50 - "8025:8025" # Web UI 51 - "1025:1025" # SMTP 52 53volumes: 54 pgdata: 55 redisdata:
Development vs Production Dockerfile
dockerfile1# Stage: dependencies 2FROM node:22-alpine AS deps 3WORKDIR /app 4COPY package.json package-lock.json ./ 5RUN npm ci 6 7# Stage: dev (hot reload, debug tools) 8FROM node:22-alpine AS dev 9WORKDIR /app 10COPY --from=deps /app/node_modules ./node_modules 11COPY . . 12EXPOSE 3000 13CMD ["npm", "run", "dev"] 14 15# Stage: build 16FROM node:22-alpine AS build 17WORKDIR /app 18COPY --from=deps /app/node_modules ./node_modules 19COPY . . 20RUN npm run build && npm prune --production 21 22# Stage: production (minimal image) 23FROM node:22-alpine AS production 24WORKDIR /app 25RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001 26USER appuser 27COPY --from=build --chown=appuser:appgroup /app/dist ./dist 28COPY --from=build --chown=appuser:appgroup /app/node_modules ./node_modules 29COPY --from=build --chown=appuser:appgroup /app/package.json ./ 30ENV NODE_ENV=production 31EXPOSE 3000 32HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1 33CMD ["node", "dist/server.js"]
Override Files
yaml1# docker-compose.override.yml (auto-loaded, dev-only settings) 2services: 3 app: 4 environment: 5 - DEBUG=app:* 6 - LOG_LEVEL=debug 7 ports: 8 - "9229:9229" # Node.js debugger 9 10# docker-compose.prod.yml (explicit for production) 11services: 12 app: 13 build: 14 target: production 15 restart: always 16 deploy: 17 resources: 18 limits: 19 cpus: "1.0" 20 memory: 512M
bash1# Development (auto-loads override) 2docker compose up 3 4# Production 5docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Networking
Service Discovery
Services in the same Compose network resolve by service name:
# From "app" container:
postgres://postgres:postgres@db:5432/app_dev # "db" resolves to the db container
redis://redis:6379/0 # "redis" resolves to the redis container
Custom Networks
yaml1services: 2 frontend: 3 networks: 4 - frontend-net 5 6 api: 7 networks: 8 - frontend-net 9 - backend-net 10 11 db: 12 networks: 13 - backend-net # Only reachable from api, not frontend 14 15networks: 16 frontend-net: 17 backend-net:
Exposing Only What's Needed
yaml1services: 2 db: 3 ports: 4 - "127.0.0.1:5432:5432" # Only accessible from host, not network 5 # Omit ports entirely in production -- accessible only within Docker network
Volume Strategies
yaml1volumes: 2 # Named volume: persists across container restarts, managed by Docker 3 pgdata: 4 5 # Bind mount: maps host directory into container (for development) 6 # - ./src:/app/src 7 8 # Anonymous volume: preserves container-generated content from bind mount override 9 # - /app/node_modules
Common Patterns
yaml1services: 2 app: 3 volumes: 4 - .:/app # Source code (bind mount for hot reload) 5 - /app/node_modules # Protect container's node_modules from host 6 - /app/.next # Protect build cache 7 8 db: 9 volumes: 10 - pgdata:/var/lib/postgresql/data # Persistent data 11 - ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql # Init scripts
Container Security
Dockerfile Hardening
dockerfile1# 1. Use specific tags (never :latest) 2FROM node:22.12-alpine3.20 3 4# 2. Run as non-root 5RUN addgroup -g 1001 -S app && adduser -S app -u 1001 6USER app 7 8# 3. Drop capabilities (in compose) 9# 4. Read-only root filesystem where possible 10# 5. No secrets in image layers
Compose Security
yaml1services: 2 app: 3 security_opt: 4 - no-new-privileges:true 5 read_only: true 6 tmpfs: 7 - /tmp 8 - /app/.cache 9 cap_drop: 10 - ALL 11 cap_add: 12 - NET_BIND_SERVICE # Only if binding to ports < 1024
Secret Management
yaml1# GOOD: Use environment variables (injected at runtime) 2services: 3 app: 4 env_file: 5 - .env # Never commit .env to git 6 environment: 7 - API_KEY # Inherits from host environment 8 9# GOOD: Docker secrets (Swarm mode) 10secrets: 11 db_password: 12 file: ./secrets/db_password.txt 13 14services: 15 db: 16 secrets: 17 - db_password 18 19# BAD: Hardcoded in image 20# ENV API_KEY=sk-proj-xxxxx # NEVER DO THIS
.dockerignore
node_modules
.git
.env
.env.*
dist
coverage
*.log
.next
.cache
docker-compose*.yml
Dockerfile*
README.md
tests/
Debugging
Common Commands
bash1# View logs 2docker compose logs -f app # Follow app logs 3docker compose logs --tail=50 db # Last 50 lines from db 4 5# Execute commands in running container 6docker compose exec app sh # Shell into app 7docker compose exec db psql -U postgres # Connect to postgres 8 9# Inspect 10docker compose ps # Running services 11docker compose top # Processes in each container 12docker stats # Resource usage 13 14# Rebuild 15docker compose up --build # Rebuild images 16docker compose build --no-cache app # Force full rebuild 17 18# Clean up 19docker compose down # Stop and remove containers 20docker compose down -v # Also remove volumes (DESTRUCTIVE) 21docker system prune # Remove unused images/containers
Debugging Network Issues
bash1# Check DNS resolution inside container 2docker compose exec app nslookup db 3 4# Check connectivity 5docker compose exec app wget -qO- http://api:3000/health 6 7# Inspect network 8docker network ls 9docker network inspect <project>_default
Anti-Patterns
# BAD: Using docker compose in production without orchestration
# Use Kubernetes, ECS, or Docker Swarm for production multi-container workloads
# BAD: Storing data in containers without volumes
# Containers are ephemeral -- all data lost on restart without volumes
# BAD: Running as root
# Always create and use a non-root user
# BAD: Using :latest tag
# Pin to specific versions for reproducible builds
# BAD: One giant container with all services
# Separate concerns: one process per container
# BAD: Putting secrets in docker-compose.yml
# Use .env files (gitignored) or Docker secrets