✦ For everyone, free.

Practical knowledge for real and everyday life

Home

17.1 Image Practices

A focused guide to Image Practices, connecting core concepts with practical Docker and container operations.

Image practices cover the deliberate decisions made specifically around how an image itself is constructed, tagged, and maintained over its lifecycle, going beyond the build mechanics of a single Dockerfile to address how images are versioned, scanned, and kept current across the entire time they remain in use.

Tagging strategy beyond latest

Relying on the mutable latest tag, or any other mutable tag, as the primary deployment reference removes the ability to know with certainty which exact image is actually running at any given moment, since the tag's target can change without any corresponding change to whatever reference was actually deployed:

docker build -t my-api:1.4.2 .
docker tag my-api:1.4.2 my-api:latest
docker push my-api:1.4.2
docker push my-api:latest
docker run my-api@sha256:3f29a8c1d8e2b4f6a9c7d5e8b1f3a6c9d2e5f8b1a4c7d0e3f6a9c2d5e8b1f4a7

Deploying by immutable digest, or at minimum by a specific, never-reused semantic version tag, while still maintaining a convenience tag like latest for human reference, gives both certainty about what is actually deployed and a readable, familiar reference for casual use.

Minimizing layer count without sacrificing clarity

Combining related instructions into a single RUN layer reduces the number of layers an image accumulates, which matters for both image size and build performance, but should be balanced against keeping the Dockerfile readable and maintainable rather than collapsing everything into one dense, hard-to-follow instruction:

RUN apt-get update && \
    apt-get install -y curl ca-certificates && \
    rm -rf /var/lib/apt/lists/*

Cleaning up package manager cache and temporary files within the same RUN instruction that installed them, as shown here, is important specifically because each layer is permanent once committed; cleanup performed in a later, separate instruction does not actually reduce the image's size, since the earlier layer's content remains part of the image's history regardless of what a subsequent layer removes from the visible filesystem.

Choosing a base image deliberately

The right base image balances several factors, size, the specific package ecosystem and compatibility needs (particularly relevant for native dependencies and the glibc-versus-musl distinction), official maintenance and security patch cadence, and should be a deliberate choice reviewed periodically rather than a default carried forward indefinitely without reconsideration:

FROM node:20-alpine
FROM node:20-slim
FROM gcr.io/distroless/nodejs20

Each of these represents a different point on the size-versus-compatibility-versus-tooling-availability spectrum, and the right choice depends on the specific application's actual dependency requirements, not a generic preference for "smallest possible" without considering whether that smallest option actually supports everything the application genuinely needs.

Scanning images for known vulnerabilities

Running a vulnerability scanner against built images, ideally as an automated step in the build pipeline rather than only occasionally and manually, surfaces known, patchable vulnerabilities in installed packages before an image is deployed:

docker scout cves my-api:1.4.2
trivy image my-api:1.4.2

Treating scan results as actionable input, either patching the specific vulnerable component or making a documented, deliberate decision that a specific finding is acceptable risk for a specific reason, is more valuable than running scans without any defined process for what happens with their results afterward.

Generating and retaining a software bill of materials

A software bill of materials (SBOM) records exactly what packages and versions an image contains, which is valuable both for vulnerability response (knowing immediately whether a newly disclosed vulnerability affects any currently deployed image) and for compliance and audit purposes in regulated environments:

docker sbom my-api:1.4.2
syft my-api:1.4.2 -o spdx-json > my-api-1.4.2-sbom.json

Generating and retaining this record as part of the standard build pipeline, alongside the image itself, means this information is already available the moment it is needed, rather than requiring it to be reconstructed retroactively during an active vulnerability response when time matters most.

Keeping base images current through scheduled rebuilds

Because a base image referenced by a fixed digest does not automatically pick up upstream security patches, and even a mutable tag reference does not update an already-built, already-deployed image, a scheduled, routine rebuild process specifically to incorporate base image updates prevents silent staleness from accumulating unnoticed over time:

0 3 * * 1 docker build --pull --no-cache -t my-api:latest . && docker push my-api:latest

A weekly scheduled rebuild, even with no application code change, surfaces base image updates and any newly available security patches on a predictable, routine cadence rather than only when an active incident or scan draws attention to staleness.

Documenting image provenance and build metadata

Embedding build metadata, the source commit, build date, and version, directly into the image through OCI label conventions provides traceability without requiring a separate lookup system to determine exactly what a given image actually contains and where it came from:

LABEL org.opencontainers.image.revision="a1b2c3d4"
LABEL org.opencontainers.image.created="2024-06-01T12:00:00Z"
LABEL org.opencontainers.image.version="1.4.2"
docker inspect my-api:1.4.2 --format '{{json .Config.Labels}}'

This metadata becomes directly queryable from the image itself at any point in the future, which is considerably more durable than relying on external records that might be lost, inconsistent, or simply harder to cross-reference against a specific running container.

Common mistakes

  • Relying on mutable tags as the primary deployment reference, losing certainty about exactly which image is actually running at any given moment.
  • Performing cleanup of package manager caches or temporary files in a separate layer from where they were created, failing to actually reduce image size despite appearing to clean up.
  • Choosing a base image based solely on minimal size without confirming it actually supports the application's real dependency and compatibility requirements.
  • Running vulnerability scans without any defined process for acting on their results, treating the scan itself as the goal rather than the actions that should follow from it.
  • Never rebuilding images on a routine schedule, allowing base image security patches to go unincorporated indefinitely even though the image continues to be deployed.

Image practices extend well beyond the mechanics of writing a single Dockerfile, encompassing deliberate tagging and versioning discipline, ongoing vulnerability scanning with a defined response process, scheduled rebuilds to incorporate upstream security patches, and embedded provenance metadata, all of which together keep an image's entire lifecycle, not just its initial build, under deliberate, informed control.

Content in this section