dockerfile
Skill1 starsChanged 6 months agoMITView on GitHub
Create and improve Dockerfiles with multi-stage builds, caching, security hardening, and debugging guidance.
- Reads credentials
- Deletes or force-pushes
- Installs packages
We split this skill into the rules it gives an agent
6 rules. 0 are common, 5 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 6 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 10
Use this skill when writing, optimizing, or debugging Dockerfiles.
- Distinctive: line 221
Use cache mounts for package managers:
- Distinctive: line 900
Add a debug stage for troubleshooting:
- Very specific: line 1219
When developing locally, use docker-compose.yml:
- Distinctive: line 1324
NEVER copy secrets into image layers: - use BuildKit secrets or runtime environment variables.
- Distinctive: line 1328
Scan images for vulnerabilities: before deploying to production.
“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Dockerfile - Production-Ready Container Images
Use this skill when writing, optimizing, or debugging Dockerfiles. Covers multi-stage builds, layer caching, security guidance, and language-specific patterns.
When to Use
- Creating a new
Dockerfilefor any language or framework - Optimising an existing Dockerfile for smaller image size or faster builds
- Adding multi-stage builds, security hardening, or non-root user configuration
- Debugging build failures or unexpected image behavior
Dockerfile Fundamentals
Basic Structure
# Syntax version (enables BuildKit features)
# syntax=docker/dockerfile:1
# Base image
FROM node:20-alpine
# Metadata
LABEL maintainer="team@example.com"
LABEL version="1.0"
# Enable pnpm via Corepack (ships with Node 16.9+)
RUN corepack enable
# Working directory
WORKDIR /app
# Copy files
COPY package.json pnpm-lock.yaml ./
# Install dependencies
RUN pnpm install --frozen-lockfile --prod
# Copy application code
COPY . .
# Expose port
EXPOSE 3000
# Default command
CMD ["node", "index.js"]Instruction Reference
| Instruction | Purpose | Usage | ||
FROM | Base image | FROM node:20-alpine | ||
WORKDIR | Set working directory | WORKDIR /app | ||
COPY | Copy files from build context | COPY src/ ./src/ | ||
ADD | Copy + extract archives (avoid unless needed) | ADD archive.tar.gz /app/ | ||
RUN | Execute command in build | RUN apt-get update && apt-get install -y curl | ||
CMD | Default command (overridable) | CMD ["python", "app.py"] | ||
ENTRYPOINT | Main executable (not overridable) | ENTRYPOINT ["docker-entrypoint.sh"] | ||
ENV | Set environment variable | ENV NODE_ENV=production | ||
ARG | Build-time variable | ARG VERSION=1.0 | ||
EXPOSE | Document port (metadata only) | EXPOSE 8080 | ||
VOLUME | Define mount point | VOLUME /data | ||
USER | Switch user | USER node | ||
HEALTHCHECK | Container health check | `HEALTHCHECK CMD curl -f http://localhost/ | exit 1` |
Multi-Stage Builds (CRITICAL for Production)
Multi-stage builds separate build dependencies from runtime dependencies, dramatically reducing image size.
Pattern: Build Stage + Runtime Stage
# syntax=docker/dockerfile:1
# ============================================
# Build Stage
# ============================================
FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
# Install dependencies (including devDependencies)
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# Build application
COPY . .
RUN pnpm run build
# ============================================
# Runtime Stage
# ============================================
FROM node:20-alpine AS runtime
RUN corepack enable
# Security: Run as non-root
USER node
WORKDIR /app
# Copy only production dependencies
COPY --chown=node:node package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --prod
# Copy built artifacts from builder stage
COPY --from=builder --chown=node:node /app/dist ./dist
# Runtime configuration
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/index.js"]corepack enable has to run in every stage that uses pnpm — each FROM starts from a clean copy of the base image, so Corepack's shims don't carry over between stages. For files with many stages, factor a shared FROM node:20-alpine AS base step that runs corepack enable once and have the other stages FROM base instead, to avoid repeating it.
Why multi-stage?
- Build stage:
pnpm install --frozen-lockfile(all deps) +pnpm run build= ~500MB - Runtime stage:
pnpm install --frozen-lockfile --prod+ built artifacts = ~150MB - Result: 70% smaller final image
Pattern: Separate Dependency Stage
For better caching when dependencies change infrequently:
# syntax=docker/dockerfile:1
# ============================================
# Dependencies Stage
# ============================================
FROM node:20-alpine AS deps
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# ============================================
# Build Stage
# ============================================
FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm run build
# ============================================
# Runtime Stage
# ============================================
FROM node:20-alpine AS runtime
RUN corepack enable
USER node
WORKDIR /app
COPY --chown=node:node package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --prod
COPY --from=builder --chown=node:node /app/dist ./dist
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/index.js"]Layer Caching Strategy
Docker caches each instruction as a layer. Order matters for build speed.
Rule: Order by Change Frequency
# ❌ BAD: Changes to code invalidate dependency cache
COPY . .
RUN pnpm install --frozen-lockfile
# ✅ GOOD: Dependencies cached separately from code
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .Optimal Ordering
- Base image (changes rarely)
- System dependencies (changes rarely)
- Language/framework dependencies (changes occasionally)
- Application code (changes frequently)
FROM python:3.12-slim # 1. Base (rarely changes)
RUN apt-get update && \ # 2. System deps (rarely changes)
apt-get install -y --no-install-recommends \
libpq-dev && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt . # 3. Language deps (occasionally changes)
RUN pip install --no-cache-dir -r requirements.txt
COPY . . # 4. Code (frequently changes)BuildKit Cache Mounts
BuildKit has been the default builder since Docker Engine 23 — no DOCKER_BUILDKIT=1 needed on current Docker versions.
Use cache mounts for package managers:
# syntax=docker/dockerfile:1
FROM node:20-alpine
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
# Cache the pnpm store across builds
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile --prod
COPY . .
CMD ["node", "index.js"]Python example:
# syntax=docker/dockerfile:1
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
# Cache pip cache across builds
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]Security Guidance
1. Use Specific Base Image Versions
# ❌ BAD: Uses latest, unpredictable
FROM node:alpine
# ⚠️ BETTER: Version pinned, but still updates
FROM node:20-alpine
# ✅ Fully pinned and reproducible
FROM node:20.11.0-alpine3.192. Run as Non-Root User
# ❌ BAD: Runs as root (default)
FROM node:20-alpine
COPY . /app
CMD ["node", "index.js"]
# ✅ GOOD: Switches to non-root user
FROM node:20-alpine
# Create user if base doesn't provide one
# RUN addgroup -g 1001 -S nodejs && \
# adduser -S nodejs -u 1001
USER node
WORKDIR /home/node/app
COPY --chown=node:node . .
CMD ["node", "index.js"]3. Minimize Attack Surface
# ❌ BAD: Full OS with unnecessary tools
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
python3 python3-pip curl wget git vim sudo
# ✅ GOOD: Minimal distroless or alpine
FROM python:3.12-slim
# Only install what's needed for runtime
# ✅ Distroless base image (no shell, no package manager)
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /app /app
CMD ["/app/main.py"]4. Scan for Vulnerabilities
# Use Docker Scout (built into Docker Desktop)
docker scout cves myimage:latest
# Or Trivy
trivy image myimage:latest
# Or Snyk
snyk container test myimage:latestIntegrate into CI:
# In your CI pipeline (e.g., GitHub Actions)
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan for vulnerabilities
run: docker scout cves myapp:${{ github.sha }} --exit-code5. Don't Embed Secrets
# ❌ BAD: Secret in layer
ENV DATABASE_PASSWORD=supersecret
# ❌ BAD: Secret in build arg (visible in history)
ARG API_KEY
RUN curl -H "Authorization: Bearer $API_KEY" ...
# ✅ GOOD: Use BuildKit secrets
# docker build --secret id=apikey,src=.apikey .
RUN --mount=type=secret,id=apikey \
curl -H "Authorization: Bearer $(cat /run/secrets/apikey)" ...
# ✅ GOOD: Pass secrets at runtime
# docker run -e API_KEY=$API_KEY myapp6. Use .dockerignore
Prevent sensitive files from entering build context:
# .dockerignore
.git
.env
.env.*
*.md
node_modules
npm-debug.log
Dockerfile
.dockerignore
.gitignore
README.md
tests/
*.test.js
coverage/
.vscode/
.idea/
*.pem
*.key
secrets/Language-Specific Patterns
Node.js / JavaScript / TypeScript
# syntax=docker/dockerfile:1
# ============================================
# Dependencies Stage
# ============================================
FROM node:20-alpine AS deps
RUN corepack enable
WORKDIR /app
# Copy package files
COPY package.json pnpm-lock.yaml ./
# Install all dependencies (including dev)
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# ============================================
# Build Stage
# ============================================
FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy source code
COPY . .
# Build (TypeScript, bundling, etc.)
RUN pnpm run build
# ============================================
# Runtime Stage
# ============================================
FROM node:20-alpine AS runtime
RUN corepack enable
# Run as non-root
USER node
WORKDIR /app
# Copy package files
COPY --chown=node:node package.json pnpm-lock.yaml ./
# Install only production dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --prod
# Copy built artifacts
COPY --from=builder --chown=node:node /app/dist ./dist
# Environment
ENV NODE_ENV=production
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD ["node", "dist/index.js"]Python
# syntax=docker/dockerfile:1
# ============================================
# Build Stage
# ============================================
FROM python:3.12-slim AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
libpq-dev && \
rm -rf /var/lib/apt/lists/*
# Copy requirements
COPY requirements.txt .
# Install to /install directory
RUN pip install --prefix=/install --no-cache-dir -r requirements.txt
# ============================================
# Runtime Stage
# ============================================
FROM python:3.12-slim AS runtime
# Install runtime dependencies only
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libpq5 && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -m -u 1001 appuser
USER appuser
WORKDIR /app
# Copy installed packages from builder
COPY --from=builder --chown=appuser:appuser /install /usr/local
# Copy application code
COPY --chown=appuser:appuser . .
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["python", "app.py"]With Poetry:
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder
# Install poetry
RUN pip install poetry
WORKDIR /app
# Copy dependency files
COPY pyproject.toml poetry.lock ./
# Install dependencies to system (not venv)
RUN poetry config virtualenvs.create false && \
poetry install --no-dev --no-interaction --no-ansi
# ============================================
# Runtime Stage
# ============================================
FROM python:3.12-slim
RUN useradd -m -u 1001 appuser
USER appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appuser /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --chown=appuser:appuser . .
ENV PYTHONUNBUFFERED=1
CMD ["python", "app.py"]Go
# syntax=docker/dockerfile:1
# ============================================
# Build Stage
# ============================================
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
# Download dependencies (cached layer)
RUN go mod download
# Copy source code
COPY . .
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# ============================================
# Runtime Stage (Scratch or Distroless)
# ============================================
FROM gcr.io/distroless/static-debian12
WORKDIR /app
# Copy binary from builder
COPY --from=builder /app/main .
USER nonroot:nonroot
EXPOSE 8080
CMD ["./main"]Alternative with scratch:
FROM scratch
# Copy SSL certificates for HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy binary
COPY --from=builder /app/main /main
EXPOSE 8080
CMD ["/main"]Rust
# syntax=docker/dockerfile:1
# ============================================
# Build Stage
# ============================================
FROM rust:1.76-slim AS builder
WORKDIR /app
# Copy manifests
COPY Cargo.toml Cargo.lock ./
# Create dummy main to cache dependencies
RUN mkdir src && \
echo "fn main() {}" > src/main.rs && \
cargo build --release && \
rm -rf src
# Copy real source code
COPY src ./src
# Build for release (triggers rebuild due to source change)
RUN touch src/main.rs && \
cargo build --release
# ============================================
# Runtime Stage
# ============================================
FROM debian:bookworm-slim
# Install runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -m -u 1001 appuser
USER appuser
WORKDIR /app
# Copy binary from builder
COPY --from=builder --chown=appuser:appuser /app/target/release/myapp ./myapp
EXPOSE 8080
CMD ["./myapp"]Java / Maven
# syntax=docker/dockerfile:1
# ============================================
# Build Stage
# ============================================
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
# Copy pom.xml and download dependencies (cached layer)
COPY pom.xml .
RUN mvn dependency:go-offline
# Copy source and build
COPY src ./src
RUN mvn package -DskipTests
# ============================================
# Runtime Stage
# ============================================
FROM eclipse-temurin:21-jre-jammy
# Create non-root user
RUN useradd -m -u 1001 appuser
USER appuser
WORKDIR /app
# Copy jar from builder
COPY --from=builder --chown=appuser:appuser /app/target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]Static Sites (Nginx)
# syntax=docker/dockerfile:1
# ============================================
# Build Stage
# ============================================
FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
# ============================================
# Runtime Stage
# ============================================
FROM nginx:1.25-alpine
# Remove default nginx config
RUN rm /etc/nginx/conf.d/default.conf
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/
# Copy built static files from builder
COPY --from=builder /app/dist /usr/share/nginx/html
# Run as non-root (nginx user)
USER nginx
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]nginx.conf:
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}Advanced Patterns
Health Checks
# HTTP health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# TCP health check (when curl not available)
HEALTHCHECK CMD nc -z localhost 8080 || exit 1
# Custom script
HEALTHCHECK CMD /app/healthcheck.sh || exit 1Conditional Builds (Build Args)
# syntax=docker/dockerfile:1
ARG ENVIRONMENT=production
FROM node:20-alpine
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
# Install dependencies based on environment
RUN if [ "$ENVIRONMENT" = "production" ]; then \
pnpm install --frozen-lockfile --prod; \
else \
pnpm install --frozen-lockfile; \
fi
COPY . .
CMD ["node", "index.js"]Build:
# Production build
docker build --build-arg ENVIRONMENT=production -t myapp:prod .
# Development build
docker build --build-arg ENVIRONMENT=development -t myapp:dev .Dynamic Version Injection
# syntax=docker/dockerfile:1
ARG VERSION=dev
ARG COMMIT_SHA=unknown
FROM node:20-alpine
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
COPY . .
# Inject version info
ENV APP_VERSION=${VERSION}
ENV APP_COMMIT=${COMMIT_SHA}
CMD ["node", "index.js"]Build:
docker build \
--build-arg VERSION=$(git describe --tags) \
--build-arg COMMIT_SHA=$(git rev-parse HEAD) \
-t myapp:latest .BuildKit Secrets for Private Packages
Private npm packages:
# syntax=docker/dockerfile:1
FROM node:20-alpine
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
# Mount .npmrc as secret (pnpm reads the same .npmrc registry/auth config as npm)
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
pnpm install --frozen-lockfile --prod
COPY . .
CMD ["node", "index.js"]Build:
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .Private git repos:
# syntax=docker/dockerfile:1
FROM golang:1.22-alpine
WORKDIR /app
# Mount SSH key as secret
RUN apk add --no-cache git openssh-client && \
mkdir -p /root/.ssh && \
ssh-keyscan github.com >> /root/.ssh/known_hosts
COPY go.mod go.sum ./
RUN --mount=type=ssh \
go mod download
COPY . .
RUN go build -o main .
CMD ["./main"]Build:
docker build --ssh default -t myapp .Debugging Containers
Add a debug stage for troubleshooting:
# syntax=docker/dockerfile:1
# ... (build stages)
# ============================================
# Runtime Stage
# ============================================
FROM node:20-alpine AS runtime
# ... (production setup)
# ============================================
# Debug Stage
# ============================================
FROM runtime AS debug
USER root
# Install debugging tools
RUN apk add --no-cache \
curl \
wget \
busybox-extras \
bind-tools \
strace \
tcpdump
USER node
CMD ["node", "--inspect=0.0.0.0:9229", "dist/index.js"]Build debug image:
docker build --target debug -t myapp:debug .
docker run -p 9229:9229 myapp:debugOptimization Techniques
Reduce Image Size
1. Use Alpine Linux
# Standard: ~900MB
FROM node:20
# Alpine: ~120MB
FROM node:20-alpine2. Use Distroless
# Alpine: ~120MB
FROM node:20-alpine
# Distroless: ~80MB
FROM gcr.io/distroless/nodejs20-debian123. Multi-stage builds (already covered)
4. Remove unnecessary files
# pnpm's content-addressable store doesn't need an explicit cache-clean step —
# use a cache mount instead so the store never lands in an image layer at all
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile --prod
RUN pip install --no-cache-dir -r requirements.txt
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*5. Optimize COPY operations
# ❌ BAD: Copies everything
COPY . .
# ✅ GOOD: Copy only what's needed
COPY src ./src
COPY package.json pnpm-lock.yaml ./Build Speed
1. Order matters (already covered)
2. Use cache mounts (already covered)
3. Parallelize independent stages
FROM base AS stage1
RUN long-running-task-1
FROM base AS stage2
RUN long-running-task-2
# Both stages build in parallel
FROM alpine
COPY --from=stage1 /output1 ./
COPY --from=stage2 /output2 ./4. Use BuildKit features
BuildKit is the default builder since Docker Engine 23 — its features (cache mounts, secrets, SSH forwarding) are available without opting in.
Reproducible Builds
1. Pin all versions
FROM node:20.11.0-alpine3.19
# Pin system packages
RUN apk add --no-cache \
curl=8.5.0-r0 \
ca-certificates=20240226-r02. Use lock files
# Node.js: pnpm-lock.yaml
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# Python: requirements.txt with pinned versions or poetry.lock
COPY requirements.txt .
RUN pip install -r requirements.txt
# Go: go.sum
COPY go.mod go.sum ./
RUN go mod downloadCommon Pitfalls
1. Running as Root
# ❌ DANGER: Runs as root
FROM node:20-alpine
COPY . /app
CMD ["node", "index.js"]
# ✅ SAFE: Switches to non-root
FROM node:20-alpine
USER node
COPY --chown=node:node . /home/node/app
CMD ["node", "index.js"]2. Installing Unnecessary Dependencies
# ❌ BAD: Installs dev dependencies in production, and doesn't fail on a lockfile drift
RUN pnpm install
# ✅ GOOD: Production only, fails the build if the lockfile is out of sync
RUN pnpm install --frozen-lockfile --prod3. Not Using .dockerignore
Without .dockerignore, every file change triggers rebuild:
# .dockerignore saves you
.git/ # Don't copy version control
node_modules/ # Don't copy local deps
*.md # Don't copy docs
.env* # Don't copy secrets4. apt-get Issues
# ❌ BAD: Stale package lists, leftover cache
RUN apt-get install -y curl
# ✅ GOOD: Update, install, cleanup in one layer
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
ca-certificates && \
rm -rf /var/lib/apt/lists/*5. Exposing Secrets in Layers
# ❌ BAD: Secret visible in image history
RUN echo "API_KEY=secret123" > .env
# ✅ GOOD: Use BuildKit secrets or runtime env vars
RUN --mount=type=secret,id=apikey \
export API_KEY=$(cat /run/secrets/apikey) && \
./configure.sh6. Using ADD Instead of COPY
# ❌ BAD: ADD has implicit behavior (URL fetch, tar extraction)
ADD https://example.com/file.tar.gz /app/
# ✅ GOOD: Use COPY (explicit) or RUN + curl
COPY file.tar.gz /app/
# OR
RUN curl -o file.tar.gz https://example.com/file.tar.gz7. Not Cleaning Package Manager Caches
# ❌ BAD: pnpm store written straight into the image layer
RUN pnpm install --frozen-lockfile --prod
# ✅ GOOD: cache mount keeps the store out of the layer entirely — nothing to clean
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile --prod8. Multiple EXPOSE Instructions
# ❌ Confusing: Multiple EXPOSE
EXPOSE 3000
EXPOSE 8080
# ✅ Clear: Single EXPOSE for primary port
EXPOSE 3000
# Document other ports in READMETesting Dockerfiles
Local Testing
# Build
docker build -t myapp:test .
# Run
docker run -p 3000:3000 myapp:test
# Check size
docker images myapp:test
# Inspect layers
docker history myapp:test
# Scan for vulnerabilities
docker scout cves myapp:testAutomated Testing
hadolint (Dockerfile linter):
# Install
brew install hadolint # macOS
# or
docker pull hadolint/hadolint
# Lint
hadolint Dockerfile
# In CI
docker run --rm -i hadolint/hadolint < DockerfileCreate .hadolint.yaml:
ignored:
- DL3008 # Pin versions in apt-get install (if you prefer not to)
trustedRegistries:
- docker.io
- gcr.ioContainer Structure Test:
# container-test.yaml
schemaVersion: 2.0.0
fileExistenceTests:
- name: 'app binary'
path: '/app/main'
shouldExist: true
commandTests:
- name: 'healthcheck'
command: 'curl'
args: ['-f', 'http://localhost:8080/health']
expectedOutput: ['OK']
metadataTest:
user: 'node'
exposedPorts: ['3000']
env:
- key: 'NODE_ENV'
value: 'production'Run:
container-structure-test test \
--image myapp:test \
--config container-test.yamlDocker Compose Integration
When developing locally, use docker-compose.yml:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: debug # Use debug stage locally
ports:
- "3000:3000"
- "9229:9229" # Debug port
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://postgres:postgres@db:5432/myapp
volumes:
- ./src:/app/src # Hot reload
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
volumes:
pgdata:CI/CD Integration
GitHub Actions Example
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: myorg/myapp
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=myorg/myapp:buildcache
cache-to: type=registry,ref=myorg/myapp:buildcache,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT_SHA=${{ github.sha }}
- name: Scan image
run: |
docker scout cves myorg/myapp:${{ github.sha }} --exit-codeRules
- ALWAYS use multi-stage builds for compiled languages or build processes - reduces image size by 50-80%.
- ALWAYS run as non-root user in final stage - critical security practice.
- ALWAYS pin base image versions with full tags (e.g.,
node:20.11.0-alpine3.19) for reproducibility. - ALWAYS create and use .dockerignore - prevents copying unnecessary files and exposing secrets.
- ALWAYS keep package manager caches out of image layers — for pnpm, use a
--mount=type=cache,id=pnpm,target=/pnpm/storemount instead of writing the store into a layer; for pip,--no-cache-dir. - ALWAYS combine apt-get update + install + cleanup in a single RUN instruction.
- NEVER use
latesttag for base images - breaks reproducibility. - NEVER copy secrets into image layers - use BuildKit secrets or runtime environment variables.
- NEVER use ADD unless you need tar extraction - COPY is explicit and safer.
- Order COPY/RUN by change frequency - dependencies first, code last, for optimal caching.
- Use
# syntax=docker/dockerfile:1at the top to enable BuildKit features. - Scan images for vulnerabilities before deploying to production.
- Test Dockerfiles with hadolint to catch common issues early.
- Use health checks to enable container orchestration platforms to monitor application health.
- Prefer alpine or distroless base images for smaller attack surface and image size.
Quick Reference
Minimal Production Dockerfile Template
# syntax=docker/dockerfile:1
# ============================================
# Build Stage
# ============================================
FROM <language>:<version>-alpine AS builder
WORKDIR /app
# Copy dependency manifests
COPY <package-files> ./
# Install dependencies
RUN <install-command>
# Copy source code
COPY . .
# Build application
RUN <build-command>
# ============================================
# Runtime Stage
# ============================================
FROM <language>:<version>-alpine AS runtime
# Run as non-root
USER <non-root-user>
WORKDIR /app
# Copy only production dependencies
COPY --chown=<user>:<user> <package-files> ./
RUN <install-prod-command>
# Copy built artifacts from builder
COPY --from=builder --chown=<user>:<user> /app/<output> ./<output>
# Set production environment
ENV <ENV_VAR>=production
# Expose port
EXPOSE <port>
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD <healthcheck-command> || exit 1
# Start application
CMD ["<entrypoint>", "<args>"]Common Commands
# Build
docker build -t myapp:latest .
# Build specific stage
docker build --target debug -t myapp:debug .
# Build with args
docker build --build-arg VERSION=1.0 -t myapp:1.0 .
# Build with secrets
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .
# Build with cache
docker build --cache-from myapp:latest -t myapp:new .
# Lint
hadolint Dockerfile
# Scan vulnerabilities
docker scout cves myapp:latest
# Check size
docker images myapp:latest
# Inspect layers
docker history myapp:latest
# Dive into layers (detailed)
dive myapp:latestResources
- Dockerfile guidance (Docker Docs)
- BuildKit Documentation
- hadolint - Dockerfile linter
- dive - Layer analysis tool
- Docker Scout - Vulnerability scanning
- Distroless Images
Inputs
- Application source directory with build tool config (
package.json,go.mod,Pipfile, etc.) - Target runtime requirements: language version, exposed ports, run command
Outputs
- A production-ready
Dockerfileusing multi-stage builds, a minimal base image, non-root user, and optimised layer caching
Examples
# Multi-stage Node.js build
FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER app
EXPOSE 3000
CMD ["node", "server.js"]Other versions7 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 7
- andypitcher/ponytail-sec★ 9same name
- Cogni-AI-OU/cogni-ai-agent-skills★ 4same name
- infiniumtek/code-review-agent★ 4same name
- AnthonyPoschen/agent-skills★ 2same name
- CorentinGC/claude-skills★ 0same name
- Jinbae10/skills★ 0same name
- StephanMeijer/skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository36 other files it gives its agents.
- skills/backend-best-practices/AGENTS.mdAGENTS.md
- adr-writerSkill
- application-healthchecksSkill
- backend-best-practicesSkill
- bspec-prd-discoverySkill
- bspec-story-implementationSkill
- bspec-story-reviewSkill
- bspec-story-syncSkill
- bspec-technical-architectureSkill
- bspec-workflowSkill
- conventional-commitsSkill
- copier-scaffoldSkill
- devenvSkill
- direnvSkill
- docker-composeSkill
- docker-setupSkill
- docs-writerSkill
- github-actions-dockerSkill
- gitleaks-setupSkill
- golang-developmentSkill
- golang-slogSkill
- golang-testingSkill
- playwright-devenvSkill
- project-webappSkill
- readme-writerSkill
- release-drafter-setupSkill
- release-drafterSkill
- release-notesSkill
- renovate-pr-triageSkill
- renovate-setupSkill
- structured-loggingSkill
- summarize-articleSkill
- summarize-youtube-videoSkill
- tailwind-v4Skill
- vicinae-extensionsSkill
- zensical-setupSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

