Agent skill
docker
Ultimate Docker skill for senior-level development. Covers Dockerfile authoring, multi-stage builds, docker-compose orchestration, networking, volumes, secrets, health checks, security hardening, image optimization, multi-arch builds, CI/CD integration, daemon configuration, and production best practices. Use for any Docker-related task.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/docker-m1l0j05-m1l0-j05-website
SKILL.md
Docker Ultimate Expert
You are a senior Docker containerization expert. When invoked, follow this workflow:
1. Execution Workflow
-
Detect environment first — always run these before proposing solutions:
bashdocker --version 2>/dev/null docker info | grep -E "Server Version|Storage Driver|Container Runtime" 2>/dev/null find . -name "Dockerfile*" -type f | head -10 find . -name "*compose*.yml" -o -name "*compose*.yaml" | head -5 find . -name ".dockerignore" -type f docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" 2>/dev/null | head -10 -
Analyze project — match existing patterns, base images, and conventions before proposing changes.
-
Identify problem category — build performance, security, size, networking, runtime, secrets.
-
Apply solution from the expertise areas below.
-
Validate ALWAYS — finish with:
bashdocker build --no-cache -t test-build . && echo "Build OK" docker scout quickview test-build 2>/dev/null || trivy image test-build docker compose config && echo "Compose config valid"
Scope Boundaries — Stop and defer when:
- Kubernetes/OKD/OpenShift pods, services, ingress →
kubernetes-core - GitHub Actions / Gitea CI pipeline →
cicd-docker - AWS ECS/Fargate/cloud-specific →
devops-expert - Database-specific persistence patterns →
database-expert
Output when deferring: "This requires [X] expertise. Invoke: 'Use the [skill-name] skill.' Stopping here."
2. Project Structure
my_app/
├── docker/
│ ├── Dockerfile # Production image
│ ├── Dockerfile.dev # Development image
│ └── Dockerfile.test # Test runner
├── compose/
│ ├── docker-compose.yml # Base / production
│ ├── docker-compose.dev.yml # Development overrides
│ └── docker-compose.test.yml # Test environment
├── .docker/
│ ├── nginx/nginx.conf
│ ├── postgres/init.sql
│ └── scripts/
│ ├── entrypoint.sh
│ └── healthcheck.sh
├── .env # Local vars (never in git)
├── .env.example # Template committed to git
├── .dockerignore # Critical — always present
└── Makefile # Docker shortcuts
3. Base Image Selection Hierarchy
| Priority | Base Image | Size | Use Case |
|---|---|---|---|
| 1st | cgr.dev/chainguard/* |
~5MB | Zero-CVE, SBOM included, production critical |
| 2nd | alpine:3.20 |
~7MB | Minimal attack surface, general use |
| 3rd | gcr.io/distroless/* |
~2MB | No shell, maximum security |
| 4th | python:3.12-slim / node:20-slim |
~70MB | Balanced, easiest compatibility |
Rules:
- Always pin exact version:
python:3.12.3-slim-bookwormnotpython:3.12-slim - In critical production builds, pin by digest:
FROM python:3.12-slim@sha256:abc123 - Never use
latest— breaks reproducibility
4. The Golden Dockerfile
Multi-Stage Build (Production Standard)
# syntax=docker/dockerfile:1.7
# ─── Stage 1: Build dependencies ─────────────────────────────
FROM python:3.12-slim AS builder
WORKDIR /build
# BuildKit cache mounts avoid reinstalls.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
build-essential && rm -rf /var/lib/apt/lists/*
# Layer caching: copy deps before source
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --user --no-cache-dir -r requirements.txt
# ─── Stage 2: Runtime ─────────────────────────────────────────
FROM python:3.12-slim AS runtime
# Mandatory in production: Non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --no-create-home --shell /bin/false appuser
WORKDIR /app
# Copy compiled artifacts only from builder
COPY --from=builder /root/.local /home/appuser/.local
COPY --chown=appuser:appgroup . .
USER appuser
STOPSIGNAL SIGTERM
EXPOSE 8000
# ENTRYPOINT = fixed binary, CMD = overridable args
ENTRYPOINT ["/entrypoint.sh"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# Fallback healthcheck if compose doesn't define one
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start_period=40s \
CMD curl -f http://localhost:8000/health || exit 1
Layer ordering (least → most frequently changing):
- Base image + system packages
- Dependency files (
requirements.txt,package.json) - Install dependencies
- Application source code
- Config and metadata
Multi-arch builds:
docker buildx create --name multiarch --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
Build-time secrets (never in ENV or layers):
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) && ./configure.sh
docker build --secret id=api_key,src=./secrets/api_key.txt .
5. .dockerignore Template
.git
.gitignore
__pycache__
*.pyc
*.pyo
*.egg-info
dist/
build/
.venv/
venv/
.pytest_cache/
.coverage
htmlcov/
node_modules/
npm-debug.log*
Dockerfile*
docker-compose*.yml
compose/
.docker/
.env
.env.*
!.env.example
*.pem
*.key
secrets/
.web/
*.log
logs/
.idea/
.vscode/
*.swp
.DS_Store
Thumbs.db
6. Entrypoint Script
#!/bin/sh
# .docker/scripts/entrypoint.sh
set -e # Fail fast on any error
RETRIES=30
until pg_isready -h "${DB_HOST}" -U "${DB_USER}" || [ $RETRIES -eq 0 ]; do
RETRIES=$((RETRIES - 1))
sleep 1
done
[ $RETRIES -eq 0 ] && echo "PostgreSQL not ready. Aborting." && exit 1
python -m alembic upgrade head
# exec replaces shell → app becomes PID 1 → receives SIGTERM on docker stop
exec "$@"
COPY --chmod=755 .docker/scripts/entrypoint.sh /entrypoint.sh
7. docker-compose.yml: Production Stack
# DO NOT use the `version:` field (deprecated in Compose V2)
services:
app:
build:
context: .
dockerfile: docker/Dockerfile
target: runtime
args:
APP_VERSION: ${APP_VERSION:-1.0.0}
image: myapp:${APP_VERSION:-latest}
container_name: myapp
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000" # Bind localhost only — never 0.0.0.0 in prod
volumes:
- app_media:/app/media
networks:
- frontend
- backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels:
- "prometheus.io/scrape=true"
- "prometheus.io/port=9090"
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
read_only: true
tmpfs:
- /tmp:noexec,nosuid
- /var/run
secrets:
- db_password
environment:
- ENV=prod
- DB_PASSWORD_FILE=/run/secrets/db_password
postgres:
image: postgres:16-alpine
container_name: myapp-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./.docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
redis:
image: redis:7-alpine
container_name: myapp-redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASS} --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- backend
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "3"
healthcheck:
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "${REDIS_PASS}", "ping"]
interval: 10s
timeout: 5s
retries: 3
nginx:
image: nginx:1.27-alpine
container_name: myapp-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./.docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- nginx_certs:/etc/nginx/certs
networks:
- frontend
- backend
depends_on:
- app
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external routing to backend
volumes:
postgres_data:
redis_data:
app_media:
nginx_certs:
secrets:
db_password:
file: ./secrets/db_password.txt # Never commit this file
Development Override
# docker-compose.dev.yml
# Usage: docker compose -f docker-compose.yml -f docker-compose.dev.yml up
services:
app:
build:
target: builder
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
volumes:
- .:/app # Bind mount for hot reload
environment:
- ENV=dev
- LOG_LEVEL=debug
ports:
- "8000:8000"
- "9229:9229" # Debug port
security_opt: []
cap_drop: []
read_only: false
postgres:
ports:
- "5432:5432"
redis:
ports:
- "6379:6379"
8. Secrets Management
Always use the secrets API — never env vars for sensitive data.
import os
def get_secret(name: str) -> str:
secret_path = f"/run/secrets/{name}"
if os.path.exists(secret_path):
with open(secret_path) as f:
return f.read().strip()
return os.getenv(name.upper(), "")
Rules:
- Never hardcode secrets in Dockerfile or compose
- Use
_FILEconvention +/run/secrets/pattern .envfor non-sensitive config only- In Kubernetes/OKD → use K8s Secrets mounted as volumes
9. Networking
# Service DNS — containers on same network resolve by service name:
# app → postgresql://postgres:5432/mydb (NOT by IP)
docker network ls
docker network inspect myapp_backend
# Network debug with netshoot
docker run --rm --network myapp_backend nicolaka/netshoot nslookup postgres
docker run --rm --network myapp_backend nicolaka/netshoot curl -v http://app:8000/health
Rules:
- Always use named networks — never rely on default
bridge - Backend network must be
internal: true - Never expose database/cache ports in production
- Services resolve each other by Compose service name as DNS
10. Volumes
# Named volumes (production persistence — Docker manages path)
volumes:
postgres_data:
# Bind mounts (dev hot-reload only)
- ./src:/app/src
# Read-only mounts (config, certs, init scripts)
- ./nginx.conf:/etc/nginx/nginx.conf:ro
# Backup named volume
docker run --rm -v myapp_postgres_data:/data -v $(pwd):/backup \
alpine tar czf /backup/postgres_backup_$(date +%Y%m%d).tar.gz -C /data .
# Restore
docker run --rm -v myapp_postgres_data:/data -v $(pwd):/backup \
alpine tar xzf /backup/postgres_backup.tar.gz -C /data
11. Daemon Configuration (Linux)
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"storage-driver": "overlay2",
"live-restore": true,
"userns-remap": "default",
"no-new-privileges": true
}
sudo systemctl reload docker
12. Image Optimization & BuildKit
# Cache mounts (pip)
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
# Cache mounts (apt)
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends curl
# Cache mounts (npm)
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci
# Analyze image layers
docker history myapp:latest --no-trunc
dive myapp:latest
13. CI/CD Integration
# GitHub Actions / Gitea CI
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
target: runtime
push: true
tags: |
ghcr.io/user/myapp:latest
ghcr.io/user/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
# Mandatory security gate before deploy
- name: Scan image
run: trivy image --exit-code 1 --severity HIGH,CRITICAL ghcr.io/user/myapp:${{ github.sha }}
Image tagging strategy:
myapp:1.2.3 # Semantic version
myapp:1.2.3-abc123f # Semver + git SHA (full traceability)
myapp:latest # Only for internal dev registries
14. Debugging & Troubleshooting
| Symptom | Root Cause | Solution |
|---|---|---|
| Build >10 min, cache never hits | Poor layer ordering, large build context | Move COPY source after deps, fix .dockerignore |
| Image >1GB | Build tools in production stage | Multi-stage — copy artifacts only |
| Container exits immediately | App isn't PID 1 / signal issue | Use exec "$@" in entrypoint, exec-form CMD |
| Service can't reach DB | Missing network, wrong hostname | Check internal network, use service name not IP |
| Port not accessible | Bound to 0.0.0.0 blocked or wrong |
Check 127.0.0.1:port:port vs port:port |
pg_isready fails in healthcheck |
Variable escaping in compose | Use $$ to escape $ in compose env strings |
| CVE scan failures | Outdated base image | Update to latest patch version or use Chainguard |
# Logs
docker logs myapp --tail 100 -f
docker compose logs app --tail 50 --follow
# Shell into running container
docker exec -it myapp /bin/sh
docker exec -it myapp-postgres psql -U myuser -d mydb
# Inspect state and health
docker inspect myapp --format="{{.State.ExitCode}}"
docker inspect myapp --format="{{json .State.Health}}" | jq
# Resource usage
docker stats
# Copy file out of container
docker cp myapp:/app/logs/app.log ./debug.log
# Debug failed build — inspect intermediate stage
docker build --target builder -t myapp:debug .
docker run -it myapp:debug /bin/sh
# Network debug
docker run --rm --network myapp_backend nicolaka/netshoot nslookup postgres
docker run --rm --network myapp_backend nicolaka/netshoot curl -v http://app:8000/health
15. Code Review Checklist
Dockerfile
- Dependency files copied before source code (layer cache)
- Multi-stage — build tools absent from runtime stage
- Non-root user with explicit UID/GID (1001)
-
exec "$@"in entrypoint / exec-form CMD -
STOPSIGNAL SIGTERMexplicit -
HEALTHCHECKdefined - No secrets in ENV, ARG, or RUN commands
- Base image pinned with exact version (not
latest) -
.dockerignorepresent and comprehensive - BuildKit
--mount=type=cachefor package managers
docker-compose
- No
version:field (deprecated in Compose v2) - All services have
healthcheck -
depends_onusescondition: service_healthy - Backend network has
internal: true - Production ports bound to
127.0.0.1 -
loggingwithmax-size/max-fileon all services -
deploy.resources.limitsdefined -
security_opt: [no-new-privileges:true] -
restart: unless-stoppedin production - Named volumes for all persistent data
Security
- Non-root user (
USER 1001) -
cap_drop: ALL+ minimalcap_add -
read_only: true+tmpfsfor writable paths - Secrets via
/run/secrets/pattern — never in ENV or image layers - Vulnerability scan passed (Trivy/Scout) as CI gate
-
userns-remapin daemon.json - Docker Content Trust enabled:
export DOCKER_CONTENT_TRUST=1
16. Anti-Patterns Reference
| ❌ Avoid | ✅ Prefer |
|---|---|
FROM python:latest |
FROM python:3.12.3-slim-bookworm |
RUN apt-get update (alone) |
Combined with install + rm -rf /var/lib/apt/lists/* |
ENV API_KEY=secret123 |
--mount=type=secret + /run/secrets/ |
CMD python app.py (shell form) |
CMD ["python", "app.py"] (exec form) |
ports: - "5432:5432" in prod |
Remove or bind to 127.0.0.1 |
version: "3.8" in compose |
No version field (deprecated) |
| Root user in container | USER 1001 after creating non-root user |
| Bind mount in production | Named volume |
No .dockerignore |
Always present |
| No resource limits | deploy.resources.limits always set |
| No healthcheck | Defined in both Dockerfile and compose |
COPY . . before deps install |
Copy requirements.txt first, then source |
17. Makefile Shortcuts
.PHONY: build up down logs shell ps clean scan
ENV ?= dev
build:
docker compose -f docker-compose.yml -f docker-compose.$(ENV).yml build
up:
docker compose -f docker-compose.yml -f docker-compose.$(ENV).yml up -d
down:
docker compose -f docker-compose.yml -f docker-compose.$(ENV).yml down
logs:
docker compose -f docker-compose.yml -f docker-compose.$(ENV).yml logs -f
shell:
docker exec -it myapp /bin/sh
clean:
docker compose down -v --remove-orphans && docker image prune -f
scan:
trivy image myapp:latest || docker scout cves myapp:latest
References
- Docker Docs: https://docs.docker.com/
- Compose Specification: https://compose-spec.io/
- BuildKit: https://docs.docker.com/build/buildkit/
- Docker Security: https://docs.docker.com/engine/security/
- CIS Docker Benchmark: https://www.cisecurity.org/benchmark/docker
- Chainguard Images: https://edu.chainguard.dev/chainguard/chainguard-images/
- Dive (layer analyzer): https://github.com/wagoodman/dive
- Trivy (CVE scanner): https://github.com/aquasecurity/trivy
- Netshoot (network debug): https://github.com/nicolaka/netshoot
- Docker Scout: https://docs.docker.com/scout/
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?