docker-ops
SKILL.md
Docker Operations
Comprehensive Docker patterns for building, running, and composing containerized applications.
Dockerfile Best Practices
| Practice | Do | Don't |
|---|---|---|
| Base image | FROM node:20-slim |
FROM node:latest |
| Layer caching | Copy dependency files first, then source | COPY . . before RUN install |
| Package install | apt-get update && apt-get install -y ... && rm -rf /var/lib/apt/lists/* |
Separate RUN for update and install |
| User | USER nonroot (create if needed) |
Run as root in production |
| Multi-stage | Separate build and runtime stages | Ship compiler toolchains |
| Secrets | --mount=type=secret (BuildKit) |
COPY .env . or ARG PASSWORD |
| ENTRYPOINT vs CMD | ENTRYPOINT for fixed binary, CMD for defaults |
Relying on shell form for signal handling |
| WORKDIR | WORKDIR /app |
RUN cd /app && ... |
| .dockerignore | Include .git, node_modules, __pycache__ |
No .dockerignore at all |
| Labels | LABEL org.opencontainers.image.* |
No metadata |
Multi-Stage Build Decision Tree
Choose your runtime base image by language:
Go ──────────── CGO disabled? ──── Yes ──► scratch or distroless/static
No ───► distroless/base or alpine
Rust ─────────── Static musl? ──── Yes ──► scratch or distroless/static
No ───► distroless/cc or debian-slim
Node.js ──────── Need native? ──── Yes ──► node:20-slim
No ───► node:20-alpine (smaller)
Python ────────── Need C libs? ─── Yes ──► python:3.12-slim
No ───► python:3.12-slim (still slim)
Java ──────────── JRE only ──────────────► eclipse-temurin:21-jre-alpine
See:
references/multi-stage-builds.mdfor complete annotated examples per language.
Layer Caching Rules
Docker caches each layer. A cache miss at layer N invalidates all subsequent layers.
What Invalidates Cache
| Trigger | Effect |
|---|---|
Changed file in COPY/ADD |
Invalidates this layer + all below |
Changed RUN command text |
Invalidates this layer + all below |
Changed ARG value |
Invalidates from the ARG declaration down |
--no-cache flag |
Invalidates everything |
| Base image update | Invalidates everything |
Optimal Layer Order
# 1. Base image (changes rarely)
FROM python:3.12-slim
# 2. System dependencies (changes rarely)
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 3. Dependency files (changes occasionally)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. Application code (changes frequently)
COPY src/ ./src/
# 5. Runtime config (changes frequently)
CMD ["python", "-m", "app"]
Rule of thumb: Order layers from least-frequently-changed to most-frequently-changed.
.dockerignore Essentials
# Version control
.git
.gitignore
# Dependencies (rebuilt in container)
node_modules
__pycache__
*.pyc
.venv
vendor/
# Build artifacts
dist/
build/
target/
*.egg-info
# IDE and editor
.vscode
.idea
*.swp
*.swo
# Docker files (prevent recursive context)
Dockerfile*
docker-compose*
.dockerignore
# Environment and secrets
.env
.env.*
*.pem
*.key
# Documentation and tests (unless needed)
docs/
tests/
*.md
LICENSE
Why it matters: Without .dockerignore, docker build sends the entire context directory to the daemon. A .git folder alone can add hundreds of megabytes.
Docker Compose Quick Reference
Service Definition
services:
web:
build:
context: .
dockerfile: Dockerfile
target: production # Multi-stage target
image: myapp:latest
ports:
- "8080:8000"
environment:
DATABASE_URL: postgres://db:5432/app
env_file:
- .env
volumes:
- ./src:/app/src # Bind mount (dev)
- app-data:/app/data # Named volume (persistent)
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped
networks:
- backend
Volumes and Networks
volumes:
app-data: # Named volume (Docker-managed)
db-data:
driver: local
networks:
backend:
driver: bridge
frontend:
driver: bridge
See:
references/compose-patterns.mdfor full patterns including profiles, watch mode, and override files.
Security Quick Reference
| Area | Recommendation |
|---|---|
| User | Run as non-root: RUN adduser -D appuser && USER appuser |
| Base image | Pin digest: FROM python:3.12-slim@sha256:abc123... |
| Filesystem | Read-only root: docker run --read-only --tmpfs /tmp |
| Capabilities | Drop all, add needed: --cap-drop=ALL --cap-add=NET_BIND_SERVICE |
| Secrets | BuildKit secrets: RUN --mount=type=secret,id=key cat /run/secrets/key |
| Scanning | Scan images: trivy image myapp:latest or grype myapp:latest |
| No latest | Always use specific tags and pin versions |
| Minimal image | Use distroless or scratch when possible |
| No SUID | RUN find / -perm /6000 -type f -exec chmod a-s {} + |
| Network | Use internal networks for backend services |
Non-Root User Pattern
# Debian/Ubuntu-based
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser
COPY . /app
USER appuser
# Alpine-based
RUN addgroup -S appuser && adduser -S -G appuser appuser
COPY . /app
USER appuser
# Distroless (built-in nonroot user)
FROM gcr.io/distroless/static:nonroot
USER nonroot:nonroot
Common Gotchas
| Gotcha | Problem | Fix |
|---|---|---|
| Large images | Shipping build tools, node_modules in final image | Multi-stage builds |
| Cache busting | COPY . . before RUN npm install |
Copy lockfile first, install, then copy source |
| Secrets in layers | COPY .env . or ARG SECRET=... bakes secrets into image history |
Use --mount=type=secret or runtime env vars |
| PID 1 problem | App doesn't receive SIGTERM, zombie processes | Use tini as init or exec form for CMD |
| Timezone | Container uses UTC | Set TZ env var or install tzdata |
| DNS caching | Alpine musl DNS issues | Use RUN apk add --no-cache libc6-compat or switch to slim |
| apt cache | apt-get update cached from old layer |
Always combine update && install in one RUN |
| Missing signals | Shell form (CMD npm start) wraps in /bin/sh |
Exec form: CMD ["node", "server.js"] |
| Build context size | Sending GB of data to daemon | Add .dockerignore, check with docker build --progress=plain |
| Layer explosion | Each RUN creates a layer | Chain related commands with && |
PID 1 / Signal Handling Fix
# Option 1: Use tini as init process
RUN apt-get update && apt-get install -y --no-install-recommends tini \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["tini", "--"]
CMD ["node", "server.js"]
# Option 2: Docker init flag (Docker 23.0+)
# docker run --init myapp
# Option 3: Node.js - handle signals in code
# process.on('SIGTERM', () => { server.close(); process.exit(0); });
Essential Docker Commands
# Build
docker build -t myapp:1.0 .
docker build -t myapp:1.0 --target production . # Multi-stage target
docker build --no-cache -t myapp:1.0 . # Force rebuild
# Run
docker run -d --name myapp -p 8080:8000 myapp:1.0
docker run --rm -it myapp:1.0 /bin/sh # Interactive debug
docker run --read-only --tmpfs /tmp myapp:1.0 # Read-only root
# Inspect
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
docker history myapp:1.0 # Layer breakdown
docker inspect myapp:1.0 | jq '.[0].Config' # Image config
# Debug running container
docker exec -it myapp /bin/sh
docker logs -f myapp
docker stats myapp
# Cleanup
docker system prune -a --volumes # Remove everything unused
docker image prune -a # Remove unused images
Reference Files
| File | Contents |
|---|---|
references/multi-stage-builds.md |
Per-language multi-stage patterns (Go, Rust, Node, Python) |
references/compose-patterns.md |
Compose services, networking, profiles, watch, overrides |
references/optimization.md |
Image size, BuildKit, security scanning, debugging |
Weekly Installs
3
Repository
0xdarkmatter/claude-modsGitHub Stars
9
First Seen
6 days ago
Security Audits
Installed on
amp3
cline3
openclaw3
opencode3
cursor3
kimi-cli3