Containerization
Containers are unit boundaries. Build them small, run them immutably, and treat them as cattle — not pets.
Dockerfile Best Practices
- Use pinned, minimal base images:
node:22-alpinenotnode:latest. Re-pin regularly on a schedule. - Order layers from least-to-most volatile:
FROM→RUN apt-get→COPY package.json→RUN npm install→COPY . .. Build cache is invalidated at the first changed layer. - Combine
RUNsteps that share a logical operation into one to minimize layer count and leak of intermediate files. - Use multi-stage builds: compile/install in one stage, copy only artifacts to the final stage. Excludes dev tools, source code, and build caches from the image.
- Run the final process as a non-root user:
RUN adduser --disabled-password app && USER app. - Use
COPY --chown=app:appinstead of a separateRUN chown. - Set
ENTRYPOINTto the process,CMDto its default arguments — so callers can override args without replacing the binary.
Image Hygiene
- Keep images under 200 MB where feasible. Audit with
docker image historyanddive. .dockerignoremust exclude:node_modules,.git, build artifacts,.envfiles, and test directories.- Never bake secrets into image layers. Use build secrets (
--secret id=...) or mount at runtime. - Tag images with the git commit SHA in CI:
myapp:$GIT_SHA.latestis a debugging alias, not a deployment target. - Scan images with
docker scoutor Trivy before pushing to a registry.
Kubernetes Manifests
- Set
resources.requestsandresources.limitson every container. Missing requests prevent the scheduler from placing pods correctly. - Use
livenessProbeandreadinessProbeon all long-running containers.readinessProbegates traffic;livenessProberestarts stuck processes. - Set
terminationGracePeriodSecondshigh enough for in-flight requests to drain (30sis often too low for HTTP services). - Use
PodDisruptionBudgetfor stateful sets and critical services to prevent zero-availability during rolling updates. - Apply labels consistently:
app.kubernetes.io/name,app.kubernetes.io/version,app.kubernetes.io/component. - Never use
hostNetwork: trueorprivileged: trueunless the workload genuinely requires it.
ConfigMaps and Secrets
- Mount secrets as files, not environment variables — env vars appear in crash dumps and
psoutput. - Rotate secrets by updating the Secret object and rolling the Deployment — pods do not automatically reload mounted secrets.
- Use
SecretProviderClass(CSI driver) or an operator to sync secrets from a vault into Kubernetes rather than storing them in etcd.
Checklist
- Dockerfile uses pinned base image and multi-stage build.
- Image runs as non-root with read-only root filesystem where possible.
-
.dockerignoreexcludes secrets,.git, and build artifacts. - Image scanned for CVEs before pushing.
- All pods have resource requests/limits, liveness, and readiness probes.
- Secrets mounted as files, not env vars.
-
PodDisruptionBudgetin place for critical workloads.