Containers & Images
Containers package an app with everything it needs, so it runs the same everywhere. Done well, they are reproducible and secure. Done carelessly, they ship known vulnerabilities, bloat, secrets, and root processes straight to production. Build small, pinned, non-root images from trusted bases, and scan them.
A container image is a build artifact like any other, and the same disciplines apply: know exactly what is in it, keep it minimal, do not bake in secrets, and keep it patched. The most common beginner mistakes are using a huge or untrusted base image, running as root, copying secrets into a layer, and never rebuilding to pick up security fixes.
Smaller images are not just faster. They have less attack surface and fewer vulnerabilities to track. Pinned, reproducible builds mean what you tested is what runs. Build and deploy images only through the pipeline (see CI/CD & Deployment). Never hand-build and push them.
Build images well
- DoStart from a minimal, official, trusted base image, pinned to a specific digest or version. Do not use
latestfrom an unknown source. - DoKeep images small (multi-stage builds, no build tools in the runtime image). That means less to ship, less to attack, and less to patch.
- DoRun as a non-root user with a read-only filesystem where possible, and expose only the ports you need.
- DoBuild reproducibly with pinned dependencies, and build and publish images only through the pipeline (see Infrastructure as Code, CI/CD).
- AvoidInstalling extra packages "just in case". Every addition is more surface and more CVEs to track.
- NeverBake secrets, keys, or credentials into an image or its layers. They stay in the history even if you delete them later. Inject them at runtime from the vault or managed identity.
FROM ubuntu:latest # huge, unpinned
COPY . . # copies .env with secrets
# runs as root by default
Unpinned and large (lots of CVEs), secrets copied into a layer that stays in the history forever, and running as root. This is a worst-case image on three counts.
FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:...
USER app # non-root
# secrets injected at runtime via managed identity, not copied in
Minimal pinned base, runs unprivileged, and no secret in the image. It is small, reproducible, and far less to attack or patch.
Run and maintain them
- DoScan images for vulnerabilities in the pipeline and fail on serious findings. Rebuild regularly to pick up base-image patches (see Vulnerability Management).
- DoPull from our trusted registry, and verify image integrity (digests or signatures) where available (see Dependency & Supply-Chain Security).
- DoSet health checks and sensible resource limits so the orchestrator can restart and bound containers (see Designing for Failure, Cost & Scale Planning).
- AvoidRunning outdated images for months. An image is a snapshot that ages, and old images build up known vulnerabilities.
- NeverRun a container as root, or with the host's secrets or broad access mounted in, when it does not need them. Least privilege applies to containers too.
Self-review checklist
- AskIs the base image minimal, trusted, and pinned to a specific version/digest?
- AskIs there any secret baked into the image or its layers?
- AskDoes it run as non-root with only the access it needs?
- AskIs it scanned, and is it being rebuilt to pick up patches?