Docker Hardening Guide

Docker container hardening checklist

A container that passes your CVE scanner is not a hardened container. Vulnerability scanning tells you what software versions are installed. Hardening tells you whether the runtime environment limits what a compromised process can actually do. This guide covers the seven checks that matter most — each one independently verifiable, each one automatically scored by Keelix.

Check 01OWASP Rule #2

Run as a non-root user

Running as root — avoid
FROM node:20-alpine
# no USER directive → runs as root
Non-root user — correct
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
USER app
Check 02OWASP Rule #3

Drop all capabilities; add back only what you need

Recommended runtime flags
docker run \
--cap-drop all \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges \
myimage
Check 03Critical finding in Keelix

Never use --privileged

Check 04OWASP Rule #6

Keep the seccomp profile active (or apply a custom one)

Check 05OWASP Rule #6

Apply AppArmor (or SELinux) confinement

Check 06OWASP Rule #7

Set memory and CPU limits

docker-compose.yml — resource limits
services: app: image: myimage deploy: resources: limits: cpus: "1.0" memory: 512M reservations: cpus: "0.25" memory: 128M
Check 07OWASP Rule #8

Use a read-only filesystem where possible

Runtime example
docker run --read-only --tmpfs /tmp myimage

What Keelix verifies automatically

Example Keelix findings — container hardening
CRITContainer "agent" running with --privileged — all kernel capabilities, no seccomp
WARNContainer "api" running as root — no USER directive in image
WARNContainer "worker" has no memory limit — denial-of-service risk
PASSContainer "db" — non-root user, cap-drop all, seccomp active
Each finding maps to a CIS Docker Benchmark or OWASP control.

Frequently asked

What does it mean to harden a Docker container?
Hardening means reducing the attack surface of a running container so that if it is compromised, the damage is contained. The key levers are: running as a non-root user, dropping Linux capabilities the process doesn't need, applying a seccomp or AppArmor profile to restrict kernel access, never using --privileged, setting memory and CPU limits, and making the container filesystem read-only where possible. Each check is independent — a container that passes one still needs the others.
Why shouldn't I run Docker containers as root?
If a process inside a root-owned container escapes the container — via a kernel vulnerability, a misconfiguration, or a vulnerable dependency — the attacker lands on the host as root. Running the container as a non-root user (USER in the Dockerfile, or -u at runtime) means a container escape grants at most the privileges of that unprivileged user on the host, which is a much smaller blast radius. The OWASP Docker Security Cheat Sheet (Rule #2) makes this the first line of defense. [OWASP Docker Cheat Sheet Rule #2 ↗]
What is --cap-drop all and which capabilities do I add back?
Linux capabilities break root's all-or-nothing privilege model into roughly 40 discrete permissions. Docker grants a default subset at startup. --cap-drop all removes every capability, then --cap-add lets you add back only what the process actually needs. Most web servers and databases run fine with NET_BIND_SERVICE (bind to ports below 1024) and nothing else. Audit tools that need to inspect the filesystem may need DAC_READ_SEARCH. The principle: enumerate required capabilities, add only those, drop everything else.
What does Docker's default seccomp profile block?
Docker's default seccomp profile blocks approximately 44 system calls out of 300+, preventing containers from loading kernel modules, creating new namespaces, manipulating the system clock, accessing hardware I/O ports, and exploiting io_uring (a kernel interface with a history of container-escape CVEs). The profile is an allowlist: it denies access by default and permits only the system calls most applications need. For most workloads, the default profile is the right baseline — custom profiles are worth pursuing only for high-assurance services. [Docker Docs — Seccomp profiles ↗]
When is --privileged ever acceptable?
Almost never in production. --privileged grants all Linux capabilities and disables seccomp and AppArmor profiles — it is functionally equivalent to running the process as root directly on the host. The only legitimate uses are very narrow: certain container-in-container CI setups, kernel-level diagnostic tools, and specialized hardware drivers. If you find --privileged in a docker-compose.yml for an application workload, remove it. Keelix flags --privileged as a critical finding.

Verify every hardening check automatically.

Free, local-first, Apache-2.0. Keelix scores your container configuration alongside host, exposure, and AI/MCP findings — one scan, one number.

operator@host — keelix
# install — free & open source (Apache-2.0)
$ curl -fsSL https://keelix.dev/install.sh | sh
$ keelix scan