plugins/docker-deploy/skills/docker-deploy/SKILL.md
Use when creating Dockerfiles, docker-compose configs, or containerizing applications. Covers multi-stage builds, image optimization, compose patterns for dev and production, health checks, signal handling, and security hardening.
npx skillsauth add sagargupta16/claude-skills docker-deployInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Task | Approach |
|------|----------|
| New Dockerfile | Choose template by language below |
| Optimize image | Multi-stage build + Alpine/distroless base |
| Dev environment | docker-compose with hot-reload and volumes |
| Production | Multi-stage, non-root user, health checks |
| Debugging | docker logs, docker exec, build with --progress=plain |
# Build stage -- check https://hub.docker.com/_/python for latest
FROM python:3.13-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Runtime stage
FROM python:3.13-slim
WORKDIR /app
RUN adduser --disabled-password --no-create-home appuser
COPY --from=builder /install /usr/local
COPY . .
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# Build stage -- check https://hub.docker.com/_/node for latest LTS
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# Runtime stage
FROM node:22-alpine
WORKDIR /app
RUN adduser -D appuser
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json .
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
# Build stage -- check https://hub.docker.com/_/golang for latest
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/server .
# Runtime stage
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
USER nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
# Build stage -- check https://hub.docker.com/_/rust for latest
FROM rust:1.82-slim AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src
COPY . .
RUN cargo build --release
# Runtime stage
FROM gcr.io/distroless/cc-debian12
COPY --from=builder /app/target/release/app /app
USER nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]
services:
app:
build: .
ports:
- "8000:8000"
volumes:
- .:/app # Hot-reload via volume mount
- /app/node_modules # Exclude node_modules from mount
environment:
- DEBUG=true
depends_on:
db:
condition: service_healthy
db:
image: mongo:8
ports:
- "27017:27017"
volumes:
- db_data:/data/db
healthcheck:
test: mongosh --eval "db.runCommand('ping').ok"
interval: 10s
timeout: 5s
retries: 3
volumes:
db_data:
services:
app:
image: ghcr.io/org/app:latest
ports:
- "8000:8000"
env_file: .env
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
depends_on:
db:
condition: service_healthy
db:
image: mongo:8
volumes:
- db_data:/data/db
restart: unless-stopped
healthcheck:
test: mongosh --eval "db.runCommand('ping').ok"
interval: 30s
timeout: 10s
retries: 3
volumes:
db_data:
Always create a .dockerignore to keep images small and avoid leaking secrets:
.git
.gitignore
node_modules
__pycache__
*.pyc
.env
.env.*
.vscode
.idea
*.md
!README.md
docker-compose*.yml
Dockerfile*
.dockerignore
tests/
coverage/
.pytest_cache/
dist/
build/
| Use Case | Base Image | Size |
|----------|-----------|------|
| Python apps | python:<version>-slim | ~150MB |
| Python minimal | python:<version>-alpine | ~50MB |
| Node.js apps | node:<lts>-alpine | ~130MB |
| Go apps | gcr.io/distroless/static | ~2MB |
| Rust apps | gcr.io/distroless/cc | ~20MB |
| Static files | nginx:alpine | ~40MB |
Order instructions from least to most frequently changing:
# 1. Base image (rarely changes)
FROM python:3.13-slim
# 2. System deps (rarely changes)
RUN apt-get update && apt-get install -y --no-install-recommends curl
# 3. App deps (changes when lockfile changes)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. App code (changes most often - LAST)
COPY . .
# HTTP health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=15s \
CMD curl -f http://localhost:8000/health || exit 1
# TCP health check (no curl needed)
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD nc -z localhost 8000 || exit 1
Use exec form for CMD/ENTRYPOINT to receive signals correctly:
# GOOD: exec form - PID 1, receives SIGTERM
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
# BAD: shell form - shell is PID 1, app doesn't get signals
CMD uvicorn main:app --host 0.0.0.0
For Node.js, handle SIGTERM explicitly:
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
});
| Practice | Implementation |
|----------|---------------|
| Non-root user | RUN adduser -D appuser + USER appuser |
| No secrets in image | Use env vars or mounted secrets at runtime |
| Pin base image digests | FROM python:3.13-slim@sha256:abc... for production |
| Scan for vulnerabilities | docker scout cves or trivy image |
| Read-only filesystem | docker run --read-only --tmpdir /tmp |
| No unnecessary packages | --no-install-recommends for apt |
| Don't | Do Instead |
|-------|-----------|
| Run as root | Create and switch to non-root user |
| Skip .dockerignore | Always create one (prevents leaking .env, .git) |
| COPY . . before installing deps | Copy lockfile first, install, then copy code |
| Use latest tag in production | Pin specific versions or digests |
| Store secrets in image layers | Use runtime env vars or mounted secrets |
| Install dev dependencies in prod image | Use multi-stage builds |
| Use alpine with Python C extensions | Use slim instead (musl vs glibc issues) |
| Ignore SIGTERM handling | Use exec form CMD + signal handlers |
testing
Use when the user asks to audit a session for uncaptured learnings. Activates on "audit this session", "session audit", "what did we miss", "end of session check", or "/starter-session-audit". Scans the conversation for corrections, preferences, decisions, and new context, then proposes where to save each.
testing
Use when setting up new repositories, auditing existing ones, or preparing repos for public visibility. Generates .gitignore, .env.example, README, and LICENSE files. Detects committed secrets and flags security issues.
tools
Use when triaging open Renovate PRs across your own repos into merge / close / defer. Activates on "renovate triage", "review dep PRs", "monthly deps", or on the 1st of a month if deps are grouped monthly.
development
Use when restructuring code without changing behavior -- extracting functions, renaming, moving files, reducing duplication, migrating between patterns (JS to TS, CJS to ESM), or addressing code smells. Covers safe refactoring workflows for any language.