Why standards documents fail
Most engineering standards share a fate. Someone writes a thorough page, everyone agrees with it, and six months later the Dockerfiles in new services look nothing like it. The document was too long to read, too generic to apply, and unconnected to the tools that engineers use daily. Meanwhile new hires learn Docker by copying whichever service is nearest, including its bad habits.
The final deliverable of our Docker containerisation service is designed to avoid that fate. It captures everything the engagement established, in a form that teams read, copy and enforce. It also underpins ongoing managed DevOps work, where we keep these standards current.
What good looks like
- A single page of rules, each with a reason, that fits on a couple of screens.
- A tested template per language or service type.
- Automated checks that give feedback in the pull request.
- An onboarding path a new developer follows in their first day.
- An owner, and a place to propose changes.
- Examples drawn from your actual services, not from a generic tutorial.
What goes into the standard
We group the rules into a short set of sections. Each rule is phrased as an instruction with its reason:
Base images. Use an approved image from the list, pinned to a version; never latest. Reason: reproducible builds and one place to patch. See base image standardisation.
Structure. Use multi-stage builds, with build tools only in builder stages. Reason: smaller images and fewer vulnerabilities. See multi-stage builds.
Security. Run as a non-root numeric user, never bake secrets into layers, use secret mounts for build-time credentials. Reason: limits damage and keeps secrets out of image history. See hardening.
Caching. Copy dependency manifests before source, keep a .dockerignore. Reason: fast builds. See layer caching.
Operations. Use exec-form CMD, handle SIGTERM, log to stdout, expose a health endpoint. Reason: orchestrators can manage the container cleanly.
Metadata. Include standard OCI labels such as source repository and revision. Reason: you can trace any running image back to its code.
Where a rule has an escape hatch, we say how to request an exception.
Templates and automation
A rule is much easier to follow when a working file shows it. We keep a templates directory, each template tested by CI, so it cannot rot:
docker-standards/
README.md
templates/
node-service.Dockerfile
python-service.Dockerfile
go-service.Dockerfile
java-service.Dockerfile
.hadolint.yaml
review-checklist.md
Linting turns the standard into feedback within minutes of opening a pull request. A minimal hadolint configuration looks like this:
# .hadolint.yaml
failure-threshold: warning
trustedRegistries:
- registry.example.com
ignored:
- DL3008 # apt version pinning: handled by the base image update policy
Each ignored rule carries a comment explaining why. Hadolint checks the Dockerfile itself; a vulnerability scan covers the result, as described in Trivy scanning. Together with the build pipeline, these form the enforcement layer.
The review checklist
A short checklist helps reviewers give consistent feedback, and helps authors self-review before opening a pull request:
- Base image is on the approved list and pinned.
- Final stage contains no build tools.
- Container runs as non-root.
- No secrets, tokens or
.envfiles copied in. - Dependencies are installed before source is copied.
.dockerignoreexists and is current.- Health check or readiness endpoint is documented.
- Image builds and runs locally using the documented command.
The onboarding guide
The onboarding guide is aimed at an engineer who has never seen your system. A good structure:
- Before you start. Required tools and versions, and access to request.
- Run the stack. The single command from your Compose setup, and what success looks like.
- Build an image. How to build, run and inspect it, including a short explanation of layers.
- Ship a change. How a pull request becomes an image in the registry and then a deployment.
- When something breaks. Common errors with fixes, and who to ask.
We ask someone who has not worked on the project to follow it unaided. Wherever they stall, we fix the document or the underlying tooling.
A template with the reasons written in
Templates teach best when the comments explain the choice, not the syntax. A fragment from a Node template might read:
# Approved base, pinned by Renovate. Do not use :latest.
FROM node:22-bookworm-slim AS runtime
# Numeric user so Kubernetes can verify runAsNonRoot.
USER 10001:10001
# Exec form so the process receives SIGTERM directly.
CMD ["node", "server.js"]
A new engineer who copies this learns three rules without reading the standard. That is the goal: the document supports the templates, and the templates carry most of the teaching.
Keeping the standard alive
We add three habits that stop decay. A named owner reviews proposed changes on a fixed rhythm, so the standard changes through pull requests rather than hallway agreements. Every Dockerfile incident gets a short note asking whether a rule or a lint check would have prevented it. And the onboarding guide is re-tested whenever the platform changes, for example after a registry move or a new CI system. These cost very little and are the difference between a living standard and a stale page.
Decisions and trade-offs
Strict standard or guidelines? Strict rules help when many teams ship independently. A small team that talks daily may only need a template and a checklist. We scale the formality to your size.
Central or per-repository? A central standards repository gives consistency; copies in each repository give autonomy. We usually centralise templates and the lint config, and let repositories reference them.
When this is overkill. If you have two services and two engineers, a README and a template is plenty.
Common failure modes
- Standards written by committee with no examples.
- No owner, so the document ages.
- Rules that conflict with reality, such as requiring a base image that cannot run your language.
- Docs that duplicate the code and diverge from it.
Delivery and handover
We deliver the documents, templates, lint configuration and CI jobs into your repositories, run a walkthrough session with your engineers, and name an owner on your side. If you also retain us for managed operations, we keep it current as the platform changes.
How to tell it is working
New services start from a template and pass lint on the first push, review comments about Dockerfiles become rare and quick, a new hire builds and runs the system in their first day, and the standard gets edited by your own engineers as things change. To discuss your documentation, contact us.