The problem with every team choosing their own
Open ten repositories and you will often find eight base images. One service is on an old Ubuntu release, another on Alpine, a third on a language image from three years ago, and a fourth that pulls latest. Each choice made sense once. Together they mean a critical OpenSSL advisory becomes eight separate investigations, scanner output is a wall of noise, and nobody can say how many services are affected.
Standardising the starting point is among the cheapest security and maintenance wins in containerisation. It sits within our Docker containerisation service and supports managed DevOps engagements where we maintain the pipeline long term.
What good looks like
- A written list of approved base images per language and purpose (build, runtime, debug).
- Each image has an owner and a reason it is on the list.
- Versions are pinned, and updates arrive as reviewed pull requests.
- Images are mirrored into your own registry, so builds do not depend on public rate limits.
- Services are rebuilt on a schedule, not only when a developer touches them.
- Anything outside the list is possible, but visible.
Choosing what goes on the list
| Purpose | Common choice | Reason |
|---|---|---|
| General runtime | Debian slim variant of the language image | Compatible, well patched, familiar |
| Static or Go services | Distroless static or scratch | Minimal attack surface |
| JVM services | Distroless Java or a Temurin runtime image | Matches your Java version policy |
| Build stages | Full language image with toolchain | Never shipped, so size does not matter |
| Debugging | A debug variant with a shell | Used by engineers, not deployed by default |
Smaller vendor-maintained minimal image families, such as those built around Wolfi, are also worth evaluating if you want very frequent patching with a minimal footprint. We do not push a particular vendor. We compare the options against your languages, support needs and licensing.
The trade-offs between Alpine, slim and distroless are covered in more depth in Dockerfile multi-stage builds.
Pinning and automated updates
A reasonable policy is: Dockerfiles reference a specific version tag, and a bot keeps it fresh. Renovate can also pin digests and open pull requests when either the tag or the digest moves. A starting configuration looks like this:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", "docker:pinDigests"],
"packageRules": [
{
"matchDatasources": ["docker"],
"matchUpdateTypes": ["minor", "patch", "digest"],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
},
{
"matchDatasources": ["docker"],
"matchUpdateTypes": ["major"],
"automerge": false
}
]
}
Automerge is only safe where CI is strong. If your tests are thin, leave automerge off and review the pull requests. The Dockerfile result looks like:
FROM node:22-bookworm-slim@sha256:<digest-managed-by-renovate> AS runtime
The digest is maintained by the tool, so a human never types it.
Rebuilds are the real patching mechanism
A patched base image fixes nothing until a service is rebuilt and redeployed on top of it. Many teams forget this: the base is updated upstream in March, and a service that nobody has changed since January keeps running the old one. We handle this in two ways:
- Scheduled rebuilds. A weekly pipeline rebuilds and redeploys through the normal promotion path, so patches land without a code change.
- Urgent path. When a critical vulnerability appears, a documented procedure lists affected services from scanner results and triggers rebuilds in priority order.
Mirroring and supply chain
Pulling directly from a public registry in CI creates two risks: rate limits that break builds, and no control over what changed upstream. We mirror approved images into your private registry, scan them there, and point Dockerfiles at the mirror. For extra assurance, the mirrored image can be signed so that clusters only admit images that went through this path.
Decisions and trade-offs
Build your own golden image or use upstream? Keep any custom layer thin. The more you add, the more you must patch and test. If the only difference is a CA bundle, handle it in the Dockerfile template instead.
Strict enforcement or guidance? For regulated environments, enforce with an admission policy that rejects unapproved registries. For fast-moving product teams, begin with CI warnings and tighten later.
One image for all languages? No. A per-language list is more useful than a generic base.
Common failure modes
- Automerge without tests breaking runtime behaviour after a minor distribution change.
- Digest pins with no updater, leaving old images forever.
- Golden images that grow into undocumented platforms.
- Forgotten long-tail services that never rebuild. We track image age as a metric.
A worked example: one advisory, two estates
Suppose a critical vulnerability is published in a widely used system library. In an estate with no standard, someone must search every repository for FROM lines, guess which images include the library, and ask each team whether they can rebuild this week. The answer takes days and is never complete.
In a standardised estate, the question is mechanical. The approved images are few, so you check whether the affected library is in them. Your registry knows which services were built on which base digest, so the affected list is a query rather than a survey. Renovate has usually already opened the update pull request, so the work is reviewing, merging and letting the rebuild pipeline roll it out. That difference in response time is the real return on standardisation, and it is hard to see until the first serious advisory arrives.
What we ask you to decide before we start
Standards only stick when someone owns them. Before the engagement begins we ask for three decisions: who approves additions to the list, how long a team may stay on an unapproved image before it must migrate, and who is on call for urgent rebuilds. Without these, the approved list slowly turns back into a suggestion. If you have no platform team yet, we can hold these roles temporarily under managed DevOps and hand them over once someone is hired.
Handling exceptions
Some workloads genuinely need something different: a machine learning image with GPU drivers, a legacy application pinned to an old runtime, or a vendor-supplied image you cannot modify. We record these in an exceptions register with an owner, a reason and a review date. Exceptions are fine. Invisible exceptions are the problem.
Delivery and handover
We deliver the approved list, the Renovate configuration, the rebuild schedule, the urgent patch runbook, and documentation in the Dockerfile standards guide.
How to tell it is working
The number of distinct base images falls and stays low, median image age is measured in weeks rather than months, scanner findings stop spiking from old services, and a major advisory is answered with a list and a plan within hours. To review your estate, book a consultation.