Small, safe container images: non-root, slim, reproducible
Updated

Three image habits that prevent most container incidents: run as non-root, keep it slim, and make rebuilds reproducible.
Most container incidents come from three habits, not from orchestrators: images run as root, images carry a full OS nobody audits, and tags float so nobody knows what is running. Fix the image and half the runtime scares disappear.
The three habits
Run as non-root. Create a named user, own only the files the app needs, and verify the process user at runtime. Root inside the container should require justification, not be the default.
Keep it slim. Start from a minimal base, install only runtime packages, and delete caches in the same layer. Every extra binary is a vulnerability report you will read later.
Pin and reproduce. Pin base versions, pin package versions, and rebuild regularly to prove the recipe still works. An image that builds only on one laptop is a rumor, not an artifact.
Worked example: a fictional image goes on a diet
The context below is fictional. Fictional service ParcelTrack ships a 900 MB image as root from a floating base tag; security scans flag dozens of packages nobody recognizes, and a restart once served a different library version than the tested one.
Week one: named user app, files owned by app, runtime check confirms non-root. Week two: minimal base, three runtime packages, caches cleaned in-layer; size drops under 150 MB and the scan list shrinks to packages the team can name. Week three: pinned base digest plus a weekly rebuild job; the rebuild proves reproducibility instead of assuming it. Same app, same features, a fraction of the risk surface.
Decision table: image choices
| Choice | Pick | Why |
|---|---|---|
| User | Named non-root | Removes the easiest escalation prize |
| Base | Minimal, pinned | Less to audit, same bytes every build |
| Signals | Handle SIGTERM gracefully | Orchestrators stop containers; crashes on stop become incidents |
| Health | A real healthcheck | Dead-but-running is the worst state to detect late |
Checklist: an image you would run at night
- Process user is non-root, verified at runtime.
- No floating tags anywhere in the chain.
- Scan output names only packages the team recognizes.
- SIGTERM shuts down cleanly and the healthcheck proves liveness.
Related reading
- Build it: Reproducible Non-Root Image.
- Then operate it: Healthcheck, SIGTERM and Limits.
Straight answers
Frequently asked questions
Why is running as root so bad?
A container escape or a writable mount turns root inside into leverage outside. A named non-root user removes the easiest prize.
Slim or alpine or distroless?
Slim first for debuggability, then smaller as the team matures. The direction matters more than the starting base.
latest tag: ever fine?
Never for anything you run twice. Pin the digest or version so Tuesday rebuilds what Monday tested.