Dockerfile Authoring Skill
What This Skill Does
Creates enterprise-grade, production-ready Dockerfiles following the Universal Dockerfile Platinum+ Standard with:
- Multi-stage builds (deps → builder → runner)
- BuildKit 1.20+ features (cache mounts, secret mounts, heredocs)
- Security hardening (non-root, distroless, read-only FS)
- Cloud-native compatibility (K8s probes, 12-factor)
What This Skill Does NOT Do
- Write docker-compose.yml files (use containerization skill)
- Create Kubernetes manifests or Helm charts
- Optimize images for size only (security takes priority)
Before Implementation
Gather context to ensure successful implementation:
| Source | Gather |
|--------|--------|
| Codebase | Package manager (package.json, pyproject.toml, go.mod), framework, existing Dockerfile |
| Conversation | Target environment, build-time secrets, runtime secrets, base image preference |
| Skill References | Language, security, BuildKit, CI/CD patterns from references/ |
| User Guidelines | Team conventions, registry requirements, compliance constraints |
Only ask user for THEIR specific requirements (domain expertise is in this skill).
Quick Pattern Selection
Language?
├─ Node.js → Framework?
│ ├─ Next.js → references/language-patterns.md#nextjs-platinum-pattern
│ ├─ Express → references/language-patterns.md#expressjs-platinum-pattern
│ └─ Bun runtime → references/language-patterns.md#nextjs-with-bun
├─ Python → Package Manager?
│ ├─ uv → references/language-patterns.md#fastapi-with-uv
│ ├─ poetry → references/language-patterns.md#fastapi-with-poetry
│ ├─ pdm → references/language-patterns.md#fastapi-with-pdm
│ └─ Django → references/language-patterns.md#django-platinum-pattern
├─ Go → CGO?
│ ├─ No → references/language-patterns.md#standard-go-service (distroless)
│ └─ Yes → references/language-patterns.md#go-with-cgo-dependencies
├─ Java → Framework?
│ ├─ Spring Boot → references/language-patterns.md#spring-boot-platinum-pattern
│ └─ Micronaut → references/language-patterns.md#micronaut-platinum-pattern
└─ Rust → Database?
├─ PostgreSQL → references/language-patterns.md#rust-with-dieselpostgresql
└─ None → references/language-patterns.md#standard-rust-service
Grep Patterns for References
# Language patterns
grep -n "### Node.js\|### Python\|### Go\|### Java\|### Rust" references/language-patterns.md
# Security patterns
grep -n "Non-Root\|Secret\|Healthcheck\|Anti-Patterns" references/security-patterns.md
# BuildKit cache mounts
grep -n "mount=type=cache\|--security\|--parents\|heredoc" references/buildkit-features.md
# CI/CD patterns
grep -n "GitHub Actions\|Kubernetes\|dockerignore" references/cicd-patterns.md
Overview
Creates Dockerfiles ensuring maximum security, determinism, and cloud-native compatibility through strict multi-stage builds, advanced BuildKit features, and modern security practices.
Required Information (Must Have)
- Language & Runtime - Node / Python / Go / Java / Rust / Bun / Deno
- Build Tool - npm / pnpm / yarn / bun / uv / poetry / pdm / pip / gradle / cargo
- Entrypoint - Command to start the app (or infer from codebase)
Optional Information (Defaults Provided)
| Information | Default | Notes | |-------------|---------|-------| | Framework | Infer from codebase | Next.js, FastAPI, Spring, Django, etc. | | Port | Common default | 3000 (Node), 8000 (Python), 8080 (Go/Java) | | Base Image | Standard/Distroless | Distroless for prod, Standard for dev | | Target Level | Platinum+ | Bronze/Silver/Gold/Platinum options available | | Build-time Secrets | Dummy values | Needed only for build (e.g., DATABASE_URL) | | Runtime Secrets | Runtime injection | Never baked into image |
Handling Missing Information
When user doesn't provide specific information:
| Scenario | Action |
|----------|--------|
| Unknown framework | Infer from codebase structure (package.json scripts, imports) |
| Unknown port | Use language default (3000/Node, 8000/Python, 8080/Go-Java) |
| Unknown base image | Default: Standard for dev, Distroless for production |
| Build-time secrets | Use dummy values, add comments to replace at build time |
| Entrypoint unknown | Infer from package.json [scripts.start] or similar |
Principle: Make reasonable defaults but add comments indicating where user should customize.
# TODO: Update port if your app uses different default
EXPOSE 3000
# TODO: Replace with your actual start command
CMD ["node", "server.js"]
🏗️ Phase 2: Apply Platinum Standards
2.1 Base Image Rules
- ONLY use: Docker Official Images or well-known vendor images (node, python, eclipse-temurin)
- Prefer: slim variants, Debian (bookworm/bullseye)
- NEVER use: latest tags, random GitHub images
- Always pin: Runtime MAJOR version, optionally MINOR for Platinum
2.2 Multi-Stage Build (MANDATORY)
Every production Dockerfile MUST include:
deps– dependency installationbuilder– compilation / buildrunner– minimal runtime No exceptions.
2.3 Dependency Installation Rules
- Copy ONLY dependency manifests first
- Lockfiles are mandatory
- Fail if lockfile mismatch
Examples:
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen
2.4 Build-time Environment Variables
Golden Rule: If a variable is needed ONLY for build, it MUST NOT exist in runtime image.
Pattern:
ARG DATABASE_URL=dummy
ENV DATABASE_URL=$DATABASE_URL
RUN build-command
ENV DATABASE_URL="" # Clear after build
NEXT_PUBLIC_*→ build-time allowed- Secrets → NEVER copied to runtime
2.5 Runtime Image Rules
Runtime image MUST contain only:
- Built artifacts
- Runtime binaries
NEVER contain:
- Package managers
- Source files (unless required)
- .env files
- Build caches
2.6 Non-Root Execution (MANDATORY)
RUN addgroup --system app && \
adduser --system --ingroup app --shell /sbin/nologin app
USER app
2.7 Healthcheck Rules
- MUST exist
- Prefer native runtime (Node/Python)
- Avoid curl unless unavoidable
Examples:
HEALTHCHECK CMD node -e "fetch('http://localhost:3000/health')..."
2.8 Entrypoint Rules
- Use exec form
- One process per container
- Correct signal handling
CMD ["node", "server.js"]
🏗️ Phase 3: Security & Performance
3.1 Security Hardening Rules (Platinum)
- No secrets in image layers
- Minimal OS packages
- Read-only root FS compatible
- PodSecurity restricted compatible
- No privileged ports (<1024)
3.2 Performance & Caching Rules
- Cache dependency installs
- Use BuildKit cache mounts when available
- Minimize layer count
- Avoid invalidating cache unnecessarily
🏗️ Phase 4: Validation & Output
4.1 Validation Checklist (Agent MUST self-check)
Before outputting Dockerfile, validate:
- [ ] No latest tags
- [ ] Lockfile used
- [ ] Non-root user
- [ ] Multi-stage build
- [ ] Secrets not leaked
- [ ] Healthcheck exists
- [ ] Entrypoint is exec-form
- [ ] Runtime image minimal
- [ ] BuildKit cache mounts used
- [ ] .dockerignore recommended if needed
4.2 Output Requirements
- Output only the Dockerfile
- Include comments explaining each stage
- Follow Platinum+ standards unless told otherwise
- Never invent dependencies
- Never guess secrets
- Recommend .dockerignore patterns when applicable
4.3 Common Build Failures & Recovery
When builds fail, diagnose and suggest fixes:
| Failure | Cause | Recovery |
|---------|-------|----------|
| Lockfile mismatch | Lockfile out of sync with manifests | Run npm install/uv sync to update lockfile |
| Module not found | Missing system dependency | Add build-essential, libssl-dev, etc. to builder stage |
| Permission denied | Non-root user can't write | Fix ownership with chown or adjust directory permissions |
| Command not found | Binary not in PATH | Use full path or ensure installed in correct stage |
| Port already in use | Conflicts with host port | Document port usage, suggest changing with --publish |
| Build cache stale | Old cached layer causing issues | Suggest docker build --no-cache |
| Secret not found | Missing --secret flag | Document required build secrets in comments |
Pattern for documenting troubleshooting in Dockerfile:
# Troubleshooting:
# - If build fails: docker build --no-cache .
# - If port conflicts: docker run -p HOSTPORT:3000 ...
# - For private packages: docker build --secret id=npm_token .
🚀 Phase 5: Platinum+ Advanced Features
5.1 Advanced BuildKit Features
Secret Mounts (for private registries, SSH keys)
Use secret mounts instead of ARG for sensitive build-time data:
# Mount secrets without leaking them into layers
RUN --mount=type=secret,id=npm_token \
npm config set //registry.npmjs.org/:_authToken=$(cat /run/secrets/npm_token) && \
npm install
Build with: docker build --secret id=npm_token,src=.npmrc .
Bind Mounts (for efficient builds)
Mount build context without copying:
# Build without copying source to image
FROM golang:1.22-slim AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
RUN --mount=type=bind,target=. \
go build -o /app/server .
SSH Mounts (for private repos)
Access private Git repositories during build:
RUN --mount=type=ssh \
go mod download
Build with: docker build --ssh default .
5.2 Multi-Platform Builds
Support multiple architectures (amd64, arm64):
ARG TARGETOS=linux
ARG TARGETARCH=amd64
FROM --platform=$TARGETOS/$TARGETARCH node:22-slim AS deps
# ... rest of build
# For Go, use build args
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -o /app/server .
Build with: docker buildx build --platform linux/amd64,linux/arm64 .
5.3 Distroless & Minimal Images
For maximum security, use distroless runtime images:
# Go application with distroless
FROM gcr.io/distroless/static-debian12 AS runner
WORKDIR /
COPY --from=builder /app/server /server
USER 65532:65532
EXPOSE 8080
CMD ["/server"]
Distroless variants:
static-debian12- Static binaries (Go, Rust)base-debian12- Minimal libc (Python, Node if needed)cc-debian12- With libgcc (some C++ apps)
Alternatives:
- Chainguard Images:
cgr.dev/chainguard/node:latest - Wolfi-based:
cgr.dev/chainguard/wolfi-base
5.4 Production Hardening
Init Process for Signal Handling
Add init process for proper signal forwarding:
# Using tini
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "server.js"]
Or use Docker's built-in init:
docker run --init my-app
Read-Only Root Filesystem
Make runtime filesystem read-only:
# Create writable directories
RUN mkdir -p /tmp /app/logs && \
chown app:app /tmp /app/logs
USER app
# Use: docker run --read-only --tmpfs /tmp my-app
5.5 .dockerignore
Always create .dockerignore to reduce build context. See references/cicd-patterns.md for complete patterns.
Reference Files
📚 Reference Documentation
Detailed patterns are organized by domain in references/:
| File | Contains |
|------|----------|
| language-patterns.md | Node.js, Python, Go, Java, Rust Dockerfiles |
| security-patterns.md | Non-root, secrets, healthchecks, anti-patterns |
| buildkit-features.md | Cache mounts, heredocs, multi-platform, optimization |
| cicd-patterns.md | GitHub Actions, Kubernetes, .dockerignore, deployment |
📖 External Documentation
For patterns not covered in this skill, consult official resources:
| Resource | URL | Use For | |----------|-----|---------| | Dockerfile Reference | https://docs.docker.com/engine/reference/builder/ | Complete Dockerfile syntax | | BuildKit Documentation | https://docs.docker.com/build/buildkit/ | BuildKit features and cache backends | | BuildKit Release Notes | https://github.com/moby/buildkit/releases | Latest syntax versions and features | | Distroless Images | https://github.com/GoogleContainerTools/distroless | Distrolesless image variants and usage | | Chainguard Images | https://edu.chainguard.dev/chainguard/chainguard-images/overview/ | Minimal alternative to distroless | | Docker Security | https://docs.docker.com/engine/security/ | Container security best practices | | Multi-Platform Builds | https://docs.docker.com/build/building/multi-platform/ | Cross-platform build strategies |
Note: Patterns in this skill reflect current best practices as of 2025. Always verify against latest official documentation.
⚠️ Critical Anti-Patterns (NEVER Do These)
❌ ALWAYS AVOID:
# NEVER use latest tags
FROM node:latest
# NEVER run as root
USER root
# NEVER bake secrets
ENV DATABASE_URL=postgresql://user:pass@prod.db:5432/db
# NEVER include package managers in runtime
RUN npm install && npm run build
# NEVER use curl for health checks
HEALTHCHECK CMD curl http://localhost:3000/health
# NEVER use shell form for CMD
CMD node server.js
✅ ALWAYS DO:
# ALWAYS pin versions
FROM node:22-slim
# ALWAYS use non-root users
RUN addgroup --system app && adduser --system --ingroup app app
USER app
# ALWAYS inject secrets at runtime
# DATABASE_URL set via docker-compose/K8s
# ALWAYS use multi-stage builds
# deps -> builder -> runner
# ALWAYS use native health checks
HEALTHCHECK CMD node -e "fetch('http://127.0.0.1:3000/health')"
# ALWAYS use exec form
CMD ["node", "server.js"]