Secure Docker Containers
You harden a Docker installation following the CIS Docker Benchmark: eliminate root containers, restrict capabilities, enable read-only filesystems, configure network isolation, and secure the Docker daemon. Prerequisite: docker-basics module. 6 tasks, about 90 minutes.
Harden Docker installation per CIS Benchmark
Securing Docker containers (per CIS benchmark)
Docker packages applications into containers — isolated units that share the OS kernel but have their own filesystems and processes. By default, however, containers are insecurely configured: they run as root, are allowed a lot, and share a network.
The CIS Docker Benchmark is a recognised industry standard of hardening recommendations. You implement the most important ones.
Key terms
- Daemon (
dockerd): the background service that manages containers. Its config lives in/etc/docker/daemon.json. - Capabilities: finely graded partial rights of
root(e.g. "open ports below 1024"). A container usually needs almost none. - User-namespace remapping: maps
rootinside the container to an unprivileged user on the host.
Your goal
You harden a Docker installation: user namespaces, non-root containers, dropping capabilities, a read-only filesystem, network isolation, and a hardened daemon. Prerequisite: the Docker Basics module.
Exercises
1. Enable user namespace remapping
Concept: user-namespace remapping. If an attacker breaks out of a container running as
root, they are root on the host too — total compromise. Remapping maps containerrootto an unprivileged host user: a breakout then lands as a harmless user. The single most important container hardening.Add to
/etc/docker/daemon.json(sudo nano /etc/docker/daemon.json):{ "userns-remap": "default" }Then restart Docker:
sudo systemctl restart dockerOn a typo Docker will not start — then check with
sudo journalctl -u docker -n 30.Check:
/etc/docker/daemon.jsoncontainsuserns-remap, andsystemctl is-active dockerreportsactive.2. Run container as non-root
Concept: the
USERdirective. Containers run asrootby default — the most dangerous Docker default. With theUSERinstruction in the Dockerfile the process runs as an unprivileged user.Create
/home/student/secure-app/Dockerfile(mkdir -p /home/student/secure-app && nano /home/student/secure-app/Dockerfile) with:FROM alpine:3.20 RUN adduser -D appuser USER appuser CMD ["sleep", "3600"]The
USER appuserline is the decisive part — the value must not berootor0.Check:
/home/student/secure-app/Dockerfilecontains aUSERdirective.3. Drop unnecessary capabilities
Concept: capabilities.
rootis split into many capabilities (e.g.NET_ADMIN,SYS_TIME). Most containers need none of them. With--cap-drop=ALLyou remove all and add back only what is needed (--cap-add) — drastically shrinking the attack surface.Build the image and start the container with capabilities dropped:
cd /home/student/secure-app docker build -t secure-app . docker run -d --name secure-app --cap-drop=ALL secure-appCheck:
docker inspect --format '{{.HostConfig.CapDrop}}' secure-appcontainsALL.4. Enforce read-only filesystem
Concept: read-only root filesystem. A write-protected container filesystem stops malware from modifying programs or nesting itself in. Where the container does need to write (e.g.
/tmp), you deliberately provide an ephemeral tmpfs.Restart the container with a read-only filesystem (remove the old one first):
docker rm -f secure-app docker run -d --name secure-app --cap-drop=ALL --read-only --tmpfs /tmp secure-appCheck:
docker inspect --format '{{.HostConfig.ReadonlyRootfs}}' secure-appyieldstrue.5. Isolate container network
Concept: internal network. A Docker network created with
--internalhas no connection to the internet. Perfect for a database that should only be reachable by the app container, never from outside — containment instead of free communication.Create an internal network:
docker network create --internal secure-netCheck:
docker network lslistssecure-net.6. Secure Docker daemon
Concept: daemon hardening. Two important daemon options:
no-new-privilegesstops processes from gaining more rights later via SUID;icc: falseforbids arbitrary containers from talking to each other unprompted (protection against lateral movement).Extend
/etc/docker/daemon.jsonso it additionally contains:{ "userns-remap": "default", "no-new-privileges": true, "icc": false }Then restart Docker:
sudo systemctl restart dockerCheck:
/etc/docker/daemon.jsoncontainsno-new-privileges(true) andicc(false).
Now practice it yourself
Reading is good – doing is better. Start this course on a real Linux VM, right in your browser. A free account is all it takes.
Start for freeLab content under CC BY 4.0 – free to use with attribution (© TechLogia).
