What a Container Hardening Pass Actually Catches, Walked Through on a Real Image
Container hardening is easier to understand as a sequence applied to one real image than as an abstract checklist. Here's that sequence, walked through against a typical application container, with what each step actually removes as an attack path.
Vendors Covered in this Article
Disclosure: We may earn a commission if you buy through some links on this page. It doesn't change what we recommend.
Start: a working image with no hardening applied
The starting point is a common pattern: a full base operating system image, the application running as root, a handful of build tools left in the final image because they were needed during the build stage, and a container that can write to most of its own filesystem. None of this is unusual; it's the default outcome of writing a Dockerfile that just works, without a second pass focused on what's actually needed at runtime.
Step 1: switch to a minimal base image
Moving from a full operating system base to a minimal one removes the shell, package manager, and utilities an attacker would use to explore the container after gaining any foothold. This is usually the single most effective change to make first, because it doesn't just reduce the attack surface, it removes the tools an attacker needs to pivot from an initial compromise into further access.
The tradeoff is debuggability: a minimal image is harder to shell into for troubleshooting. Keep a separate debug variant of the image, used only in non-production environments, rather than compromising the hardened production image for convenience.
Step 2: run as a non-root user
A process running as root inside a container that later escapes to the host, through a container runtime vulnerability or a misconfigured volume mount, hands an attacker root on the host too. Setting a non-root user in the Dockerfile means that same escape lands with far more limited privileges, turning a full compromise into a contained one.
This step often surfaces file permission issues that root silently papered over, which is worth treating as useful information: those are exactly the paths worth double-checking for correctness rather than annoyances to route around.
For example, a team switches its image to a non-root user and the application immediately fails on startup because it tries to write a cache file into a directory owned by root. The tempting fix is to loosen permissions or go back to root. The better fix is to create a dedicated writable directory, assign it to the application user, and mount it as the one place the process may write. That change both resolves the error and documents where the application writes, which makes the later read-only filesystem step much easier to apply.
Step 3: make the filesystem read-only where possible
Setting the container's root filesystem to read-only, with explicit writable volumes only where the application genuinely needs to write (a temp directory, a specific cache path), closes off a whole category of persistence technique: an attacker who gains code execution can no longer drop a file onto the container's disk to survive a restart or pivot further.
This step requires actually knowing where your application writes, which is itself a useful exercise; several teams discover their application was writing logs or temp files to unexpected locations only once a read-only filesystem made those writes fail loudly instead of silently succeeding.
Step 4: strip build-time dependencies from the final image
A multi-stage build that compiles or installs dependencies in one stage and copies only the finished artifact into a clean final stage removes compilers, package caches, and build tools that have no runtime purpose but do have a history of security advisories of their own. Every tool left in the final image is one more thing that needs patching, whether or not the application ever uses it.
What's left after the four steps, and what still needs ongoing attention
The result of these four steps together is an image with a meaningfully smaller attack surface, a limited blast radius if the application is compromised, and no leftover tooling for an attacker to use once they're in, at very little cost beyond the initial Dockerfile rework. None of it is a one-time fix, though: a hardened image built today accumulates the same kind of drift as anything else, as dependencies age and new advisories land against packages that were clean when the image was built.
Treat the hardened Dockerfile as a template to reapply, not a finished artifact, and rebuild images on a schedule even when the application code hasn't changed, so patched base images and dependencies flow through automatically instead of waiting for the next feature deploy to happen to pull them in.
Apply this sequence to every image you ship:
- Switch to a minimal base image, keeping a separate debug variant for non-production environments only.
- Run the application as a non-root user, and treat any file permission errors that surface as paths worth checking.
- Set the root filesystem to read-only, with writable volumes only where the application genuinely needs to write.
- Use a multi-stage build so compilers, package caches, and build tools never reach the final image.
- Rebuild images on a schedule, even without code changes, so patched base images and dependencies flow through automatically.
What Good Looks Like
Good container hardening means a minimal base image, a non-root runtime user, a read-only filesystem where possible, and no build-time tooling left in the final image.
Building The Capability (5-Stage Skill Ladder)
How to Get Started
Disclosure: We may earn a commission if you buy through some links on this page. It doesn't change what we recommend.
Runtime protection from CrowdStrike catches what a hardening pass can't: behavior inside a container that looks wrong even when the image itself was built correctly.
Tenable's image scanning is a direct check on step four above, flagging leftover build tooling and known vulnerabilities in whatever packages made it into the final image.
Frequently Asked Questions
How much do these changes typically affect image build time or size?
Minimal base images and multi-stage builds usually shrink the final image significantly, which tends to speed up deploys rather than slow them down. Build time may increase slightly with a multi-stage setup, but it's a one-time cost against a permanent security improvement.
Do we need to harden every container, or just the ones facing the internet?
Prioritize internet-facing and privileged containers first, but don't skip internal ones entirely. An internal service is still a step in a lateral-movement chain if something upstream of it is compromised.
How do we check whether an image already follows these practices?
Run it through an image scanner that checks for root users, writable filesystems, and known vulnerabilities in installed packages. Treat the first scan as a baseline, not a pass or fail, since most existing images will show several findings the first time.
About the numbers
This guide doesn't quote a sourced benchmark. Figures in it are estimates or general guidance, so check them against your own numbers.
Related Guides
Hardening Containers: The Checks That Actually Stop Real Attacks
Which container hardening steps actually reduce risk, versus the ones that mostly look good on a checklist without stopping much.
Hardening Containers Without Slowing Every Deploy
A practical set of container hardening steps that catch real risk, ranked by how much they actually cost your deploy pipeline in time.
Container Security: What Actually Stops an Attacker
Why passing every image scan still isn't enough, and the four separate layers, base image, build pipeline, runtime config, and behavior, that hardening covers.
Where Container Security Actually Breaks Down in Practice
Image scanning catches known vulnerabilities but misses what a container does after it starts. Here is what real container hardening also requires.
Hardening the Containers Behind Your RAG Inference Stack
A hardening checklist for the containers running your production RAG pipeline: embedding, reranking, and generation workloads, not just the application layer.
Hardening Containers Without Slowing Down Builds
A practical checklist for hardening container images and runtime configuration, including the common mistakes that quietly reopen the gaps you just closed.