docker-patterns
affaan-m/ecc
Docker and Docker Compose patterns for local development, security, networking, and multi-service orchestration.
What is docker-patterns?
Reference patterns for containerized development workflows using Docker and Docker Compose. Covers multi-stage Dockerfiles, service networking, volume strategies, container security hardening, and debugging techniques for local development and production deployments.
- Multi-stage Dockerfile patterns separating dev, build, and production stages
- Docker Compose configuration for local development with database, cache, and mail services
- Service discovery and custom networking strategies within Compose networks
- Volume management patterns including bind mounts, named volumes, and anonymous volumes
- Container security hardening: non-root users, capability dropping, read-only filesystems, and secret management
- Debugging commands for logs, container inspection, network troubleshooting, and resource monitoring
How to install docker-patterns
npx skills add null --skill docker-patternsHow to use docker-patterns
- 1.Create a docker-compose.yml file with your services (app, database, cache, etc.) using the provided patterns
- 2.Define a multi-stage Dockerfile with dev, build, and production targets
- 3.Set up volumes for source code (bind mount), dependencies (anonymous volume), and persistent data (named volume)
- 4.Configure environment variables and service dependencies with healthchecks
- 5.Use docker compose up to start development environment; use docker compose logs and docker compose exec for debugging
- 6.For production, use docker-compose.prod.yml override with resource limits and restart policies
Use cases
- Setting up a local development environment with PostgreSQL, Redis, and Node.js services
- Migrating from local development to containerized workflow
- Designing multi-container architectures with isolated networks and security boundaries
- Troubleshooting container networking, DNS resolution, or volume persistence issues
- Reviewing Dockerfiles for security best practices and image size optimization
- Full-stack developers setting up local development environments
- DevOps engineers designing containerized architectures
- Teams migrating to Docker-based workflows
- Developers troubleshooting container networking or persistence issues
docker-patterns FAQ
Use bind mounts for development source code (hot reload) and init scripts. Use named volumes for persistent data like databases that should survive container restarts.
Services resolve by name within the Compose network. For example, the 'db' service is accessible as 'db:5432' from other containers without explicit port mapping.
Bind mounting the entire directory would override the container's node_modules with the host's (which may be empty or incompatible). Anonymous volumes protect container-generated dependencies from being overwritten.
The base compose file defines the core setup; override.yml is auto-loaded for development-only settings like debug ports and log levels. Use explicit -f flags for production configs.
Always use volumes for stateful services. Named volumes persist across restarts; bind mounts preserve host directories. Without volumes, container data is lost on removal.
Full instructions (SKILL.md)
Source of truth, from affaan-m/ecc.
name: docker-patterns description: Docker and Docker Compose patterns for local development, container security, networking, volume strategies, and multi-service orchestration. metadata: origin: ECC
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
# 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
Related skills
More from affaan-m/ecc and the wider catalog.
documentation-lookup
Fetch current library and framework docs via Context7 MCP instead of relying on training data.
dotnet-patterns
Idiomatic C# and .NET patterns, conventions, and best practices for robust, maintainable applications.
dynamic-workflow-mode
Agent skill from affaan-m/ecc.
e2e-testing
Playwright E2E testing patterns with Page Object Model, CI/CD integration, and flaky test strategies.
ecc-guide
Navigate ECC's agents, skills, commands, hooks, and install profiles by reading live repository state.
ecc-recipes
Agent skill from affaan-m/ecc.