一键导入
container-patterns
Production container patterns covering multi-stage Dockerfiles, image security, build optimization, runtime security, PID 1, and Docker Compose
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Production container patterns covering multi-stage Dockerfiles, image security, build optimization, runtime security, PID 1, and Docker Compose
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Expert reference for application security — OWASP Top 10 mitigations, auth/authz, secrets management, cryptography, input validation, dependency hygiene, and secure-by-default code patterns
Expert reference for token counting, prompt compression, cost estimation, and quality preservation when optimizing prompts for Claude models
Experimentation design and A/B testing standards for product teams
Expert reference for digital accessibility — WCAG conformance, ARIA, and inclusive design patterns
Authoritative reference for agent architecture selection, multi-agent orchestration, tool design, memory systems, and failure mode prevention
Expert reference for evaluating LLM systems, RAG pipelines, and AI features in production
| name | container-patterns |
| description | Production container patterns covering multi-stage Dockerfiles, image security, build optimization, runtime security, PID 1, and Docker Compose |
| version | 1 |
RUN addgroup --system app && adduser --system --ingroup app app in the build stage. Use USER app before CMD. Root inside a container maps to root on the host if the container runtime is misconfigured or the container escapes. Non-root is a mandatory defense-in-depth layer.FROM node:20-alpine is mutable — the tag can point to a different image after a rebuild. Use FROM node:20-alpine@sha256:abc123... for reproducible, auditable builds. Update digests deliberately via Renovate or Dependabot.ENV, or ARG instructions. Every layer is permanently stored in the image and visible via docker history. Build-time secrets use BuildKit's --mount=type=secret: RUN --mount=type=secret,id=npmrc cat /run/secrets/npmrc > ~/.npmrc. Runtime secrets are injected via environment variables from a secrets manager at container start.trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest. Critical CVEs block the pipeline. Images are signed with cosign and SBOM generated (syft myapp:latest -o spdx-json > sbom.json) for supply chain attestation.terminationGracePeriodSeconds (default 30s) before SIGKILL. An app that is PID 1 and does not forward SIGTERM causes forceful kills and request drops.FROM scratch or gcr.io/distroless/static as the runtime base. Result: <15MB image, zero OS attack surface. IF the app needs glibc (most C-linked binaries) THEN use gcr.io/distroless/base. IF the app needs a shell for debugging THEN use Alpine (5MB OS, but adds musl libc — Go binaries compiled with CGO_ENABLED=0 work; CGO-enabled binaries do not without cross-compilation flags).node:20-alpine as build base, copy only node_modules (production) and compiled assets to node:20-alpine runtime stage. Never copy node_modules containing dev dependencies into the runtime image.RUN command needs a secret (npm token, pip index URL with credentials) THEN use BuildKit secret mounts. Set DOCKER_BUILDKIT=1 (default in Docker 23+). Pass: docker build --secret id=npmrc,src=$HOME/.npmrc .# syntax=docker/dockerfile:1 at the top to enable the full BuildKit frontend.CAP_NET_BIND_SERVICE is needed for ports <1024 (prefer running on port 8080+ and use Kubernetes Service to remap). CAP_SYS_PTRACE for debuggers. Never run with --privileged in production.readOnlyRootFilesystem: true in the Kubernetes securityContext. Mount writable emptyDir volumes only for specific paths (/tmp, /var/cache). Read-only root prevents most container escape and persistence techniques.depends_on with condition: service_healthy for startup ordering in development.terminationGracePeriodSeconds - 5 seconds (leave 5s buffer before Kubernetes sends SIGKILL). Typical budget: 25s for a 30s termination period.COPY . . as an early layer. Source code changes every commit; placing it early in the Dockerfile invalidates the cache for all subsequent layers including dependency installation. Order: COPY package*.json ./ → RUN npm ci → COPY . ...dockerignore file is absent THEN the build context includes .git, node_modules, test fixtures, and local secrets, making builds slow and potentially leaking sensitive files into the image. Always create .dockerignore before the first docker build.Layer Cache as a Dependency Graph
Each Dockerfile instruction creates a layer. Docker caches layers by instruction hash + parent layer hash. Any change to a layer invalidates all subsequent layers. Think of the Dockerfile as a dependency graph where the most stable content (OS packages, dependency manifests) goes at the top and the most volatile content (application source code) goes at the bottom. A single misplaced COPY . . before RUN npm install means every code change reinstalls all dependencies — a 2-minute cache miss instead of a 3-second one.
Multi-Stage Build: Build Environment vs. Runtime Environment
The build stage is a temporary factory. It needs compilers, linkers, test runners, linters, and build caches. The runtime stage is the shipping container. It needs only the artifact the factory produced — a binary, a set of JS files, a Python wheel. Nothing the factory needed belongs in the shipping container. Measure the difference: docker build --target build -t debug . && docker images debug vs. docker build -t prod . && docker images prod. A 10–50× size difference is normal and expected.
Defense in Depth for Runtime Security
Container security is a series of independent layers, each of which must fail for an attacker to succeed: (1) non-root user limits damage from app compromise, (2) read-only filesystem prevents persistence and toolchain download, (3) dropped capabilities limit what syscalls are available, (4) seccompProfile: RuntimeDefault blocks ~300 dangerous syscalls, (5) no privileged mode prevents escape to host, (6) network policies limit lateral movement. No single layer is sufficient; all five together make exploitation significantly harder.
| Term | Precise Definition |
|---|---|
| Multi-stage build | Dockerfile pattern using multiple FROM statements; only artifacts from the final stage ship |
| BuildKit | Next-generation Docker build engine with parallel stages, cache mounts, and secret mounts |
| Distroless | Google-maintained base images with only the language runtime and no shell, package manager, or OS tools |
| Layer | Immutable, content-addressed filesystem diff created by each Dockerfile instruction; cached and shared |
| .dockerignore | File listing paths excluded from the build context sent to the Docker daemon |
| PID 1 | First process in a container's PID namespace; receives signals from the container runtime |
| tini | Minimal init process (docker run --init or ENTRYPOINT ["/sbin/tini", "--"]) that reaps zombies and forwards signals |
| dumb-init | Simpler alternative to tini for signal forwarding; single static binary, no zombie reaping by default |
| SIGTERM | Signal sent by Kubernetes to PID 1 at the start of pod termination; app must handle it for graceful shutdown |
| seccompProfile | Syscall allowlist applied to a container; RuntimeDefault blocks ~300 Linux syscalls not needed by most apps |
| cosign | Sigstore tool for signing container images and verifying signatures in admission webhooks |
| SBOM | Software Bill of Materials; inventory of all packages and dependencies in an image for vulnerability tracking |
1. Single-stage build ships compiler and dev tools — image is 10× larger than necessary
Bad:
FROM node:20
WORKDIR /app
COPY . . # Copies .git, test files, local secrets
RUN npm install # Installs devDependencies too
RUN npm run build
CMD ["node", "dist/server.js"]
# Final image: ~1.2GB with full Node.js + npm + devDependencies + source
Fix (reduces to ~85MB):
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./ # Cache layer: only invalidated when deps change
RUN npm ci # Installs devDependencies for build
COPY . . # Source copied after deps for cache efficiency
RUN npm run build && npm prune --production
FROM node:20-alpine AS runtime
RUN addgroup --system app && adduser --system --ingroup app --no-create-home app
WORKDIR /app
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --from=builder --chown=app:app /app/dist ./dist
USER app
EXPOSE 8080
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]
2. Root user in container — if app is compromised, attacker has host root via container escape
Bad:
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
# Runs as root (UID 0) — default for all base images
Fix:
FROM python:3.11-slim AS runtime
RUN groupadd --gid 1001 app && \
useradd --uid 1001 --gid app --no-create-home --shell /bin/false app
WORKDIR /app
COPY --chown=app:app requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app . .
USER app # All subsequent RUN, CMD, ENTRYPOINT run as UID 1001
EXPOSE 8080
CMD ["python", "app.py"]
3. Secrets in ENV or ARG are baked into the image and visible in docker history
Bad:
ARG NPM_TOKEN
ENV NPM_TOKEN=${NPM_TOKEN} # Visible in every layer and `docker inspect`
RUN npm install
Fix (BuildKit secret mount — never written to any layer):
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci # .npmrc with token available during RUN, not stored in layer
Build command: docker build --secret id=npmrc,src=$HOME/.npmrc .
4. Missing .dockerignore sends entire working directory as build context
Bad: No .dockerignore — docker build . sends .git/ (hundreds of MB), node_modules/ (also hundreds of MB), .env (secrets), test data, and IDE files to the Docker daemon before building.
Fix — .dockerignore:
.git
.gitignore
.env
.env.*
node_modules
npm-debug.log
dist
coverage
.nyc_output
**/*.test.ts
**/*.spec.ts
**/__tests__
.pytest_cache
__pycache__
*.pyc
.DS_Store
Dockerfile*
docker-compose*
README.md
docs/
5. Application is PID 1 without signal handling — Kubernetes SIGTERM causes forceful SIGKILL after 30s
Bad:
CMD ["node", "server.js"]
# Node.js as PID 1: SIGTERM not forwarded to child processes; zombie reaping broken
# Kubernetes waits 30s then sends SIGKILL — active requests are dropped
Fix:
RUN apk add --no-cache tini # Add tini to Alpine image
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]
Also implement graceful shutdown in the application:
process.on('SIGTERM', async () => {
console.log('SIGTERM received — starting graceful shutdown');
server.close(() => { // Stop accepting new connections
db.end(); // Close DB connection pool
process.exit(0);
});
setTimeout(() => process.exit(1), 25000); // Force exit after 25s if not drained
});
Bad Dockerfile (root user, no multistage, secrets in ENV, COPY . . early, no tini):
FROM node:18
WORKDIR /app
COPY . . # Entire repo including .git, node_modules, secrets
ARG NPM_TOKEN
ENV NPM_TOKEN=${NPM_TOKEN} # Secret baked into layer — visible in docker history
RUN npm install # Installs devDependencies, uses ENV token
RUN npm run build
EXPOSE 3000
CMD ["node", "src/server.js"]
# Problems: root user, 1.3GB image, secret in layer, app is PID 1, no signal handling
Good Dockerfile (non-root, multistage, distroless runtime, BuildKit secrets, tini, layer-ordered for cache):
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
--mount=type=cache,target=/root/.npm \
npm ci # Cache mount: npm cache persists across builds
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . . # Source copied after deps — only this layer rebuilds on code change
RUN npm run build && \
npm prune --production # Remove devDependencies from node_modules
FROM gcr.io/distroless/nodejs20-debian12 AS runtime
# Distroless: no shell, no package manager, no OS tools — minimal attack surface
WORKDIR /app
COPY --from=builder --chown=nonroot:nonroot /app/node_modules ./node_modules
COPY --from=builder --chown=nonroot:nonroot /app/dist ./dist
USER nonroot # distroless provides nonroot user (UID 65532)
EXPOSE 8080
# distroless Node image uses dumb-init by default; CMD is the node arguments
CMD ["dist/server.js"]
Corresponding Kubernetes securityContext:
securityContext:
runAsNonRoot: true
runAsUser: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp # Writable tmp for apps that need it
volumes:
- name: tmp
emptyDir: {}
USER instruction before CMD/ENTRYPOINTFROM image:tag@sha256:...); Renovate or Dependabot manages digest updatesENV, ARG, or COPY — BuildKit --mount=type=secret used for build-time secrets.dockerignore present and excludes .git, node_modules/__pycache__, .env*, test files, docstini or dumb-init as ENTRYPOINT PID 1; application receives signals and implements SIGTERM handlerterminationGracePeriodSeconds - 5 seconds (default budget: 25s)trivy image --exit-code 1 --severity HIGH,CRITICAL runs in CI; critical CVEs block pipelinereadOnlyRootFilesystem: true, capabilities: drop: [ALL], seccompProfile: RuntimeDefault