Docker Hardened Images Series — Blog 5 (Final)
What We Are Building
This final blog turns everything you have learned into a practical adoption plan.
You will:
- Walk through a realistic migration of an existing Dockerfile
- Use a clear migration checklist so nothing is missed
- Understand when Community is enough and when Select/Enterprise becomes useful
- Get a short overview of hardened system packages
- Leave with a production checklist and honest limitations
- See how Docker Hardened Images and Docker Sandboxes fit together
Why This Matters
Pulling a DHI and building a new service is easy.
Migrating an existing production service without breaking it is the real work.
Most failures happen because of the same few issues:
- Missing shell or package manager in the runtime stage
- Application cannot run as non-root
- Trying to bind to a privileged port
- Forgetting that secrets and tools do not belong in the final image
This blog gives you the checklist and the judgement to avoid those problems.
What You Should Know Before Starting
- Full Hardened Images series (Blogs 1–4) completed
- At least one existing Dockerfile you can migrate (we will use a typical example)
Migration Checklist (Print This)
| Item | What to do | Why it fails if ignored |
|---|---|---|
| Base image | Change FROM to a matching dhi.io/... variant |
Still using the old attack surface |
| Package management | Install only in *-dev stage, copy artefacts |
Runtime image has no package manager |
| Shell commands | Move all RUN that need a shell into the build stage |
Runtime image has no shell |
| Non-root user | Ensure files are readable/executable by the non-root user (usually UID 65532) | Permission denied at runtime |
| Ports | Listen on ≥ 1025 inside the container | Non-root cannot bind to privileged ports |
| TLS certificates | Remove manual ca-certificates install steps |
Already present in DHI |
| ENTRYPOINT / CMD | Inspect the DHI and adjust if needed | Unexpected default entrypoint |
| Multi-stage | Always use -dev for build, pure runtime for final |
Final image becomes large and less secure |
| Secrets | Never bake secrets into layers | They will be visible in the image history |
| Policy check | Run docker scout policy ... --policy-bundle dhi/policies:latest
|
Image may not meet the hardened standard |
Step 1 — Realistic Migration Example
Before (typical official image)
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 80
CMD ["python", "app.py"]
After (proper DHI multi-stage)
# syntax=docker/dockerfile:1
FROM dhi.io/python:3.13-dev AS builder
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
RUN python -m venv /app/venv
ENV PATH="/app/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM dhi.io/python:3.13
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
ENV PATH="/app/venv/bin:$PATH"
WORKDIR /app
COPY --from=builder /app/venv /app/venv
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]
Key changes:
- Build tools only in the first stage
- Final image is minimal and non-root
- Port moved above 1024
- No unnecessary packages left behind
Build and test:
docker build -t migrated-app .
docker run --rm -p 8000:8000 migrated-app
docker scout policy migrated-app --policy-bundle dhi/policies:latest
Step 2 — Community vs Select vs Enterprise (Honest View)
| Need | Community (free) | Select | Enterprise |
|---|---|---|---|
| Minimal, near-zero CVE images | Yes | Yes | Yes |
| Signed SBOM + SLSA provenance | Yes | Yes | Yes |
| FIPS / STIG variants | No | Yes | Yes |
| 7-day SLA for critical/high CVEs | No | Yes | Yes |
| Customisations | No | Limited (5) | Unlimited |
| Hardened system packages repository | Public only | Limited | Full access |
| Extended lifecycle (post-EOL) | No | No | Yes (add-on) |
Practical recommendation
Start with Community for almost every service.
Move to Select/Enterprise only when you have a concrete compliance (FIPS/STIG) or SLA requirement.
Step 3 — Hardened System Packages (Short Overview)
Docker builds many system packages from source and signs them.
These packages are already inside the DHI base images.
Community users can also configure the public hardened package repositories in their own Dockerfiles if they need extra packages during a build stage.
Enterprise customers get a private repository with broader access and longer support.
For most applications you will never need to touch this — the base image already contains the hardened packages you need.
Step 4 — Production Adoption Checklist
- [ ] Every new service starts from a DHI base
- [ ] Existing services have a migration ticket and a deadline
- [ ] Multi-stage pattern is the team standard
- [ ] CI always authenticates to
dhi.ioand runs the DHI policy bundle - [ ] Policy failures block the merge
- [ ] Images are scanned and attested in the pipeline
- [ ] Non-root and high ports are verified in staging
- [ ] Team knows how to use
docker scout attestanddocker scout policy - [ ] Old official base images are removed from the allowed list
Step 5 — Combining with Docker Sandboxes
The two series fit together cleanly:
- Developers use Docker Sandboxes so AI agents can work safely
- Those same agents (or the CI) build application images from Docker Hardened Images
- The final artefact that reaches production is both built in an isolated environment and based on a hardened, attested base
This gives you strong isolation during development and strong supply-chain guarantees at runtime.
Realistic Limitations (Be Honest)
- Runtime images have no shell → debugging needs
docker debug, a sidecar, or a temporary-devcontainer - Some legacy applications still expect to run as root or bind to port 80 → you must change the application or use a workaround
- Community tier has no SLA — patch timing follows upstream + Docker’s cadence
- Very exotic native dependencies may not yet exist as hardened packages
- Organisation governance (for Sandboxes) and DHI Select/Enterprise are separate paid offerings
Common Migration Pitfalls
- Leaving a
USER rootstatement “just in case” - Installing packages in the final stage
- Forgetting to update health-check or readiness probes that assumed port 80
- Assuming every tag has a
-devvariant (check the catalog) - Migrating only the base image and leaving the rest of the Dockerfile unchanged
Troubleshooting Quick Reference
| Symptom | Most common cause | Fix |
|---|---|---|
exec: "bash": executable not found |
Runtime image used for a shell command | Move the command to a -dev stage |
| Permission denied | Non-root user cannot write | Fix ownership with COPY --chown or in builder |
| Address already in use / bind error | Trying to use port < 1024 | Change application to listen on ≥ 1025 |
| Policy fails on “shell present” | Final stage still has a shell | Use pure runtime variant |
| Cannot pull in CI | Missing docker login dhi.io
|
Add the login step shown in Blog 4 |
Cleanup
docker rmi migrated-app 2>/dev/null || true
What We Learned in the Whole Hardened Images Series
- Why typical images need hardening and how to pull the first DHI
- Anatomy of runtime vs dev variants and how to build real applications
- SBOMs, provenance, signatures and how to enforce the same policies
- Production multi-stage Dockerfiles + CI gates
- Migration checklist, tier decisions, limitations and how to adopt safely
You now have a complete, practical path from “I have heard of hardened images” to “I can migrate and run them in production with confidence”.
Final Combined Recommendation
- Use Docker Sandboxes for any AI coding agent work
- Use Docker Hardened Images as the base for every application image
- Enforce both with clear team defaults and automated checks
That combination gives you strong isolation during development and a minimal, attested, continuously maintained runtime in production.
References
- https://docs.docker.com/dhi/migration/checklist/ — official migration checklist
- https://docs.docker.com/dhi/ — tiers and feature comparison
- https://docs.docker.com/dhi/how-to/hardened-packages/ — system packages
- https://docs.docker.com/dhi/how-to/use/ — adoption considerations
- https://docs.docker.com/dhi/migration/ — language-specific migration guides
All technical claims verified against current official documentation (August 2026).
Top comments (0)