Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Docker and Docker Compose patterns for local development, container security, networking, volume strategies, and multi-service orchestration.
.claude/skills/loulanyue-docker-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 122% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 193% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 215% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 126% | 0% |
Docker and Docker Compose best practices for containerized development.
yaml# 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:
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"]
yaml# 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
bash# Development (auto-loads override) docker compose up # Production docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
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 containeryamlservices: 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:
yamlservices: db: ports: - "127.0.0.1:5432:5432" # Only accessible from host, not network # Omit ports entirely in production -- accessible only within Docker network
yamlvolumes: # 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
yamlservices: 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
dockerfile# 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
yamlservices: 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
yaml# 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
node_modules
.git
.env
.env.*
dist
coverage
*.log
.next
.cache
docker-compose*.yml
Dockerfile*
README.md
tests/bash# 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
bash# 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
# 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| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 14,506 | 8,409 | -42% | 1 | 1 | 0% | 2,926 | 4,045 | +38% | 0 | 0 | — |
case-02 | fail→pass | 16,213 | 12,021 | -26% | 1 | 1 | 0% | 2,958 | 4,673 | +58% | 0 | 0 | — |
case-03 | pass→pass | 9,911 | 10,384 | +5% | 1 | 1 | 0% | 1,836 | 4,148 | +126% | 0 | 0 | — |
case-04 | pass→pass | 12,060 | 8,270 | -31% | 1 | 1 | 0% | 2,335 | 4,028 | +73% | 0 | 0 | — |
case-05 | pass→pass | 10,442 | 6,915 | -34% | 1 | 1 | 0% | 1,944 | 3,578 | +84% | 0 | 0 | — |
case-06 | fail→pass | 10,201 | 8,869 | -13% | 1 | 1 | 0% | 1,763 | 3,915 | +122% | 0 | 0 | — |
case-07 | pass→pass | 6,883 | 5,174 | -25% | 1 | 1 | 0% | 1,255 | 3,286 | +162% | 0 | 0 | — |
case-08 | pass→pass | 10,105 | 7,254 | -28% | 1 | 1 | 0% | 1,848 | 3,665 | +98% | 0 | 0 | — |
case-09 | pass→pass | 8,979 | 6,481 | -28% | 1 | 1 | 0% | 1,733 | 3,437 | +98% | 0 | 0 | — |
case-10 | fail→fail | 11,572 | 9,279 | -20% | 1 | 1 | 0% | 2,067 | 4,039 | +95% | 0 | 0 | — |
case-11 | pass→pass | 7,557 | 2,892 | -62% | 1 | 1 | 0% | 1,253 | 2,830 | +126% | 0 | 0 | — |
case-12 | pass→pass | 4,135 | 2,721 | -34% | 1 | 1 | 0% | 736 | 2,810 | +282% | 0 | 0 | — |
case-13 | fail→pass | 5,765 | 4,606 | -20% | 1 | 1 | 0% | 1,102 | 3,226 | +193% | 0 | 0 | — |
case-14 | pass→pass | 4,494 | 4,239 | -6% | 1 | 1 | 0% | 798 | 3,023 | +279% | 0 | 0 | — |
case-15 | pass→pass | 9,336 | 5,652 | -39% | 1 | 1 | 0% | 1,599 | 3,375 | +111% | 0 | 0 | — |
case-16 | fail→pass | 4,770 | 3,237 | -32% | 1 | 1 | 0% | 923 | 2,907 | +215% | 0 | 0 | — |
case-17 | pass→pass | 5,627 | 3,534 | -37% | 1 | 1 | 0% | 935 | 2,889 | +209% | 0 | 0 | — |
case-18 | pass→pass | 8,124 | 4,692 | -42% | 1 | 1 | 0% | 1,552 | 3,222 | +108% | 0 | 0 | — |
case-19 | pass→pass | 16,031 | 12,408 | -23% | 1 | 1 | 0% | 2,662 | 4,399 | +65% | 0 | 0 | — |
case-20 | pass→pass | 5,621 | 7,502 | +33% | 1 | 1 | 0% | 1,157 | 3,905 | +238% | 0 | 0 | — |
case-21 | pass→pass | 6,253 | 6,681 | +7% | 1 | 1 | 0% | 1,217 | 3,622 | +198% | 0 | 0 | — |
case-22 | pass→pass | 6,476 | 7,823 | +21% | 1 | 1 | 0% | 1,347 | 3,971 | +195% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.