name: containerizing-services description: Containerizes backend services with production-grade Dockerfiles — multi-stage builds, pinned base images, non-root users, healthchecks, and small images. Use when the user asks to write or review a Dockerfile, dockerize or containerize a service or app, shrink a Docker image, or fix container build or security issues. Do not use for Kubernetes manifests or orchestration, docker-compose service topology, or CI pipeline design (shipping-with-ci-cd).
Containerizing Services
When to use / when NOT to use
- Use for: writing or reviewing Dockerfiles, image size/security hardening, build-cache problems.
- Do NOT use for: Kubernetes/orchestration, compose topology, CI pipelines (shipping-with-ci-cd), or app code changes.
Core rules
- Multi-stage: build heavy, run slim. Compile/install in a build stage; copy only artifacts into the runtime stage.
- ✅
FROM node:22.4-slim AS build…FROM node:22.4-slim+COPY --from=build /app/dist ./dist - ❌ One stage that ships compilers, dev deps, and source history.
- ✅
- Pin base images. Exact tag minimum; digest for production.
- ✅
FROM python:3.12.4-slim@sha256:… - ❌
FROM python:latest
- ✅
- Run as non-root. Create a user and switch:
USER app. Root-only containers fail most cluster policies and widen every exploit. - Order layers for cache. Copy dependency manifests and install BEFORE copying source, so code edits don't bust the dependency layer.
- ✅
COPY package*.json ./→RUN npm ci→COPY . . - ❌
COPY . .first (every commit reinstalls everything).
- ✅
- One process per container. No supervisord bundles; sidecars belong to the orchestrator. Logs go to stdout/stderr only — never files inside the container.
- Ship a
.dockerignore. At minimum:.git,node_modules/venvs,.env*, secrets, test fixtures. NeverCOPYa secret; pass at runtime. - Define
HEALTHCHECK(or document the orchestrator probe) hitting a real readiness endpoint, not/. - Default slim/distroless; alpine only knowingly. musl breaks some native wheels/binaries — use alpine only after the app is verified on it.
Workflow
- Pick runtime base (
<lang>:<exact-version>-slimdefault; distroless escape hatch for static binaries). - Write
.dockerignorebefore the Dockerfile. - Write multi-stage Dockerfile per rules 1–8.
- Build:
docker build -t svc:dev .— rebuild after touching one source file and confirm the dependency layer is cached (CACHEDin output). - Validate:
docker run --rm svc:dev id -ureturns non-zero UID;docker inspect --format='{{.Config.Healthcheck}}' svc:devis set;docker run --rm --read-only svc:devstarts (add tmpfs mounts if the app needs scratch dirs). - Check size:
docker images svc:dev— if the runtime image exceeds ~2× the artifact size, find what leaked in (docker history svc:dev).
Edge cases & failure modes
- Native deps fail on slim → install build tools in the build stage only (
apt-get install -y --no-install-recommends build-essential), never in runtime. - Secrets needed at build time →
RUN --mount=type=secret,id=npm_token …(BuildKit); neverARGa secret (it persists in history). - Image works locally, fails in cluster as non-root → files owned by root;
COPY --chown=app:app. - Timezone/CA errors in distroless → use the
:debugor cc variant, or copyca-certificatesfrom the build stage.
References
Deeper recipes and gotchas: see references/patterns.md.