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
docker-compose.yml
services : app : build : context : . target : dev
Use dev stage of multi-stage Dockerfile
ports : - "3000:3000" volumes : - . : /app
Bind mount for hot reload
- /app/node_modules
Anonymous volume -- preserves container deps
environment : - DATABASE_URL=postgres : //postgres : postgres@db : 5432/app_dev - REDIS_URL=redis : //redis : 6379/0 - NODE_ENV=development depends_on : db : condition : service_healthy redis : condition : service_started command : npm run dev db : image : postgres : 16 - alpine ports : - "5432:5432" environment : POSTGRES_USER : postgres POSTGRES_PASSWORD : postgres POSTGRES_DB : app_dev volumes : - pgdata : /var/lib/postgresql/data - ./scripts/init - db.sql : /docker - entrypoint - initdb.d/init.sql healthcheck : test : [ "CMD-SHELL" , "pg_isready -U postgres" ] interval : 5s timeout : 3s retries : 5 redis : image : redis : 7 - alpine ports : - "6379:6379" volumes : - redisdata : /data mailpit :
Local email testing
image : axllent/mailpit ports : - "8025:8025"
Web UI
- "1025:1025"
SMTP
volumes : pgdata : redisdata : Development vs Production Dockerfile
Stage: dependencies
FROM node:22-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci
Stage: dev (hot reload, debug tools)
FROM node:22-alpine AS dev WORKDIR /app COPY --from = deps /app/node_modules ./node_modules COPY . . EXPOSE 3000 CMD [ "npm" , "run" , "dev" ]
Stage: build
FROM node:22-alpine AS build WORKDIR /app COPY --from = deps /app/node_modules ./node_modules COPY . . RUN npm run build && npm prune --production
Stage: production (minimal image)
FROM node:22-alpine AS production WORKDIR /app RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001 USER appuser COPY --from = build --chown = appuser:appgroup /app/dist ./dist COPY --from = build --chown = appuser:appgroup /app/node_modules ./node_modules COPY --from = build --chown = appuser:appgroup /app/package.json ./ ENV NODE_ENV=production EXPOSE 3000 HEALTHCHECK --interval = 30s --timeout = 3s CMD wget -qO- http://localhost:3000/health || exit 1 CMD [ "node" , "dist/server.js" ] Override Files
docker-compose.override.yml (auto-loaded, dev-only settings)
services : app : environment : - DEBUG=app : * - LOG_LEVEL=debug ports : - "9229:9229"
Node.js debugger
docker-compose.prod.yml (explicit for production)
services : app : build : target : production restart : always deploy : resources : limits : cpus : "1.0" memory : 512M
Development (auto-loads override)
docker compose up
Production
docker 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 services : frontend : networks : - frontend - net api : networks : - frontend - net - backend - net db : networks : - backend - net
Only reachable from api, not frontend
networks : frontend-net : backend-net : Exposing Only What's Needed services : db : ports : - "127.0.0.1:5432:5432"
Only accessible from host, not network
Omit ports entirely in production -- accessible only within Docker network
Volume Strategies volumes :
Named volume: persists across container restarts, managed by Docker
pgdata :
Bind mount: maps host directory into container (for development)
- ./src:/app/src
Anonymous volume: preserves container-generated content from bind mount override
- /app/node_modules
Common Patterns services : app : volumes : - . : /app
Source code (bind mount for hot reload)
- /app/node_modules
Protect container's node_modules from host
- /app/.next
Protect build cache
db : volumes : - pgdata : /var/lib/postgresql/data
Persistent data
- ./scripts/init.sql : /docker - entrypoint - initdb.d/init.sql
Init scripts
Container Security Dockerfile Hardening
1. Use specific tags (never :latest)
FROM node:22.12-alpine3.20
2. Run as non-root
RUN addgroup -g 1001 -S app && adduser -S app -u 1001 USER app
3. Drop capabilities (in compose)
4. Read-only root filesystem where possible
5. No secrets in image layers
Compose Security services : app : security_opt : - no - new - privileges : true read_only : true tmpfs : - /tmp - /app/.cache cap_drop : - ALL cap_add : - NET_BIND_SERVICE
Only if binding to ports < 1024
Secret Management
GOOD: Use environment variables (injected at runtime)
services : app : env_file : - .env
Never commit .env to git
environment : - API_KEY
Inherits from host environment
GOOD: Docker secrets (Swarm mode)
secrets : db_password : file : ./secrets/db_password.txt services : db : secrets : - db_password
BAD: Hardcoded in image
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
View logs
docker compose logs -f app
Follow app logs
docker compose logs --tail = 50 db
Last 50 lines from db
Execute commands in running container
docker compose exec app sh
Shell into app
docker compose exec db psql -U postgres
Connect to postgres
Inspect
docker compose ps
Running services
docker compose top
Processes in each container
docker stats
Resource usage
Rebuild
docker compose up --build
Rebuild images
docker compose build --no-cache app
Force full rebuild
Clean up
docker compose down
Stop and remove containers
docker compose down -v
Also remove volumes (DESTRUCTIVE)
docker system prune
Remove unused images/containers
Debugging Network Issues
Check DNS resolution inside container
docker compose exec app nslookup db
Check connectivity
docker compose exec app wget -qO- http://api:3000/health
Inspect network
docker network ls docker network inspect < project
_default Anti-Patterns