DEV Community

Cover image for Your container is shipping software you've never heard of
Mr Recruiter
Mr Recruiter

Posted on

Your container is shipping software you've never heard of

Here's an uncomfortable fact about the container you're about to deploy. You wrote a small fraction of what's inside it. The rest, the base image, the OS packages, the dependencies, the dependencies of your dependencies, came from somewhere else, and most teams ship it all to production without ever really looking at what's in there. Container image security is mostly about closing that gap, knowing and controlling what's actually in the box you're running. Let me break down where it goes wrong.

Your base image is a decision, not a default

Almost every container starts FROM something, and that base image sets the tone for everything. The common instinct is to grab a big, familiar, full-featured base because it's convenient and everything you might need is already there. That convenience is a security cost. A large base image comes with a huge amount of software you're not using, and every one of those unused packages is potential attack surface, another thing that could have a vulnerability, another thing an attacker could use if they get in.

The move is to start minimal. Use small, purpose-built base images that contain close to only what your application actually needs. Less software in the image means less that can be vulnerable, less that can be exploited, and a smaller thing to reason about. Minimal base images aren't just smaller and faster, they're meaningfully more secure purely by having less in them to go wrong.

You inherit every vulnerability in everything you include

When you build an image, you inherit the security state of everything in it, the base OS, its packages, your language dependencies, and all of their transitive dependencies. And software has vulnerabilities discovered constantly, so an image that was clean when you built it accumulates known vulnerabilities over time as new ones are found in the things it contains. The image didn't change, the world's knowledge of its flaws did.

This is why image scanning matters, and why it's not a one-time thing. A scanner checks your image against databases of known vulnerabilities and tells you what's in there that's now known to be exploitable. You want this running automatically as part of your build, so a new image gets checked before it ships, and ideally you also re-scan images already in production, because something clean last month may have known holes today. Scanning isn't optional hygiene, it's how you find out what you're actually shipping before an attacker does.

Don't run as root, because why hand over the keys

By default, containers often run their processes as root, the all-powerful user. This is convenient and dangerous. If an attacker manages to break out of or compromise your application inside a container running as root, they've got far more power to cause damage than they would against a process running as a limited user. It's the difference between an intruder finding a locked interior and an intruder handed the master key on the way in.

So run your containers as a non-root user with only the permissions the app needs. It's a small change in your image definition and it meaningfully limits what a compromise can do. This is least privilege again, the same principle everywhere, don't give a process more power than its job requires, so that a compromise of it stays small.

Where your images come from matters

Pulling base images and tools from public registries is normal and fine, but it's worth being deliberate about it, because you're trusting whoever published them. Use official and trusted sources rather than random unmaintained images, because a malicious or abandoned base image is a genuine supply-chain risk, you'd be building on top of someone else's problem, or someone else's trap. Prefer well-maintained, official images, and be suspicious of pulling arbitrary images from unknown publishers into your build.

Pin what you build on, so it doesn't shift under you

A subtle one: if your build grabs "the latest" of things, your image can change unpredictably between builds, and you can't be sure what you're actually shipping, or reproduce a build to investigate a problem. Pinning to specific versions makes your builds predictable and makes it possible to know and control exactly what's in your image. It also means a bad new version of something upstream doesn't silently flow into your next build. Predictability is a security property here, because you can't secure what you can't pin down.

Don't bake secrets into images

One specific trap worth calling out: never build secrets into your container images. A key or password baked into an image is sitting in that image wherever it goes, in every registry it's pushed to, readable by anyone who can pull it, and present in the image's layers even if a later layer "removes" it. Secrets get provided to containers at runtime, from a proper secrets system, not baked in at build time. An image should be safe to store and share, and a baked-in secret makes it the opposite.

The mental model

A container image is a bundle of software, most of which you didn't write, shipped as one unit. Security is about knowing and minimizing what's in it. Start from minimal, trusted base images. Scan for known vulnerabilities automatically, and re-scan, because the vulnerabilities in a static image grow over time. Run as non-root so a compromise stays contained. Pull from trusted sources and pin versions so your builds are predictable and you know what you're running. And never bake secrets in. The theme is the same throughout: you're responsible for everything in the box, so put less in it, know what's there, and give it as little power as it needs to do its job.

Top comments (0)