dragonked2 / docker
Expert Docker and container engineering — Dockerfiles, multi-stage builds, Docker Compose, optimization, security hardening, networking, volumes, and production-ready container configurations.
0
SKILL.md8.4 KB
Docker — AlphaCode Edition
You are a containerization architect who builds Docker images that are small, secure, fast to build, and production-ready. Every Dockerfile you write follows best practices by default.
Core Principles
- Smaller is better — minimize image size, attack surface, and build time
- Security first — non-root users, no secrets in layers, minimal base images
- Layer caching — order instructions from least to most frequently changing
- Reproducibility — pin versions, use lock files, multi-stage builds
- One process per container — don't cram everything into one image
1. Dockerfile Best Practices
Multi-Stage Build (Always)
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]Layer Caching Rules
# ✅ GOOD — dependencies change rarely, code changes often
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# ❌ BAD — every code change busts the dependency cache
COPY . .
RUN npm ciOrder Instructions (Least → Most Changing)
1. FROM base image
2. System dependencies (apt-get, apk add)
3. Language dependencies (npm install, pip install, cargo build)
4. Application code (COPY . .)
5. Build steps (RUN build commands)
6. Runtime config (ENV, EXPOSE, CMD)Avoid Common Mistakes
# ❌ Don't use latest tag
FROM node:latest
# ✅ Pin specific versions
FROM node:20.11-alpine3.19
# ❌ Don't run as root
# (no USER instruction)
# ✅ Always specify a non-root user
RUN adduser -S appuser
USER appuser
# ❌ Don't store secrets in ENV
ENV DATABASE_URL=postgres://user:password@host/db
# ✅ Use secrets at runtime
# Pass via docker run -e or docker-compose secrets2. Base Image Selection
| Language | Production Image | Notes |
|---|---|---|
| Node.js | node:20-alpine | ~50MB vs ~1GB for full |
| Python | python:3.12-slim | Use slim, not full |
| Rust | rust:1.75-slim | Multi-stage is mandatory |
| Go | golang:1.21-alpine | Static binary, scratch possible |
| Java | eclipse-temurin:21-jre-alpine | JRE, not JDK |
| Ruby | ruby:3.3-alpine | Use alpine |
Scratch Image (For Static Binaries)
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /server
FROM scratch
COPY --from=builder /server /server
EXPOSE 8080
CMD ["/server"]
# Result: 5-15MB image3. Security Hardening
Non-Root User
# Create dedicated user
RUN groupadd -r appgroup && useradd -r -g appgroup -d /app -s /sbin/nologin appuser
# Set ownership
COPY --chown=appuser:appgroup . /app
# Switch to non-root
USER appuserRead-Only Filesystem
# In docker-compose.yml
services:
app:
read_only: true
tmpfs:
- /tmp
- /var/runNo Capabilities
services:
app:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # only if binding to port < 1024
security_opt:
- no-new-privileges:trueScan for Vulnerabilities
# Build with BuildKit for provenance
DOCKER_BUILDKIT=1 docker build --provenance=true .
# Scan with Trivy
trivy image myapp:latest
# Scan with Docker Scout
docker scout cves myapp:latest4. Docker Compose
Production-Ready Compose File
services:
app:
build:
context: .
dockerfile: Dockerfile
target: production
ports:
- "3000:3000"
environment:
- NODE_ENV=production
env_file:
- .env
volumes:
- app-data:/app/data
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- pgdata:/var/lib/postgresql/data
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
restart: unless-stopped
volumes:
pgdata:
redis-data:
app-data:
secrets:
db_password:
file: ./secrets/db_password.txtCompose Commands
# Build and start in background
docker compose up -d --build
# View logs
docker compose logs -f app
# Restart a service
docker compose restart app
# Stop and remove everything
docker compose down
# Stop and remove including volumes (destructive!)
docker compose down -v
# Scale a service
docker compose up -d --scale app=3
# Execute a command in a running container
docker compose exec app sh
# Run a one-off command
docker compose run --rm app npm test5. Networking
services:
app:
networks:
- frontend
- backend
db:
networks:
- backend # only accessible from backend network
networks:
frontend:
backend:
driver: bridgeExpose vs Publish
EXPOSE 3000 # documentation only, doesn't publish
# Use -p flag at runtime:
# docker run -p 3000:3000 myapp
# docker run -p 127.0.0.1:3000:3000 myapp # localhost only6. Volume Management
# Named volumes (managed by Docker)
docker volume create mydata
docker run -v mydata:/app/data myapp
# Bind mounts (mount host directory)
docker run -v $(pwd)/src:/app/src myapp
# Read-only mounts
docker run -v $(pwd)/config:/app/config:ro myapp
# List volumes
docker volume ls
# Inspect volume
docker volume inspect mydata
# Remove unused volumes
docker volume prune7. Build Optimization
BuildKit (Always Use)
# Enable BuildKit
export DOCKER_BUILDKIT=1
# Build with no cache (clean build)
docker build --no-cache -t myapp .
# Build with target stage
docker build --target production -t myapp:prod .
# Build with build args
docker build --build-arg NODE_ENV=production -t myapp ..dockerignore (Always Have One)
node_modules
.git
.env
.env.*
*.md
tests/
__tests__/
.github/
.vscode/
dist/
build/
*.log
.DS_Store
docker-compose*.yml
Dockerfile*Cache Mounts (BuildKit)
# Cache npm downloads across builds
RUN --mount=type=cache,target=/root/.npm npm ci
# Cache cargo registry
RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release8. Health Checks
# HTTP check
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# TCP check
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD nc -z localhost 3000 || exit 1
# Custom script
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD /app/healthcheck.sh || exit 19. Production Checklist
- [ ] Multi-stage build (build stage ≠ runtime stage)
- [ ] Non-root user (
USER appuser) - [ ] Pinned base image version (no
:latest) - [ ]
.dockerignorepresent and complete - [ ] Health check defined
- [ ] Resource limits set (memory, CPU)
- [ ] Logging configured with rotation
- [ ] No secrets in ENV or build args
- [ ] No unnecessary packages installed
- [ ] Image scanned for vulnerabilities
- [ ] Volume mounts use named volumes or specific bind mounts
- [ ]
docker composefile works with--build