---
title: "A5: Container Runtime Hardening Depth"
canonical: https://openvapt.com/academy/a5.html
html: https://openvapt.com/academy/a5.html
site: OpenVAPT, https://openvapt.com/
license: OpenVAPT Source-Available Licence 1.3, https://openvapt.com/legal/licence-text.html
modified: 2026-10-16
version: 0.1.0-alpha.6
---

# A5: Container Runtime Hardening Depth

Beyond "is the daemon on TCP": the daemon defaults, socket ownership, confinement profiles, resource limits and image trust that decide what a compromised container can do to its host.

- Level: Applied
- Time: about 35 minutes
- Read first: P9, P20

## You Will Be Able To

- Read /etc/docker/daemon.json and a container's hostconfig.json and name each setting that limits blast radius.
- Explain why the Docker socket and the docker group are root, and what rootless mode changes.
- Judge a running container by four questions: who is it, what can it call, how much can it use, and can it change itself.

## The Daemon Sets the Defaults

Every container inherits what the daemon does not forbid. The daemon's configuration file, /etc/docker/daemon.json, is therefore the first place to look. userns-remap maps container root to an unprivileged host user, and no-new-privileges stops setuid escalation. icc false keeps containers from talking to each other on the default bridge. live-restore keeps containers up through a daemon restart (HOST-CTR-002). The deeper checks add the settings that are easy to forget. A log driver with max-size stops one container filling the disk (HOST-CTR-005). userland-proxy false means no extra root process sits on each published port (HOST-CTR-006). default-ulimits bounds file descriptors and processes for every container (HOST-CTR-007). Insecure registries, debug and experimental flags should be off (HOST-CTR-010).

NIST SP 800-190 describes the same idea as hardening the host operating system and the container runtime together. The runtime's configuration is part of the host's attack surface, not something the images can fix later.

## The Socket Is Root

The Docker API has no concept of a limited user. Anyone who can send it requests can start a privileged container with the host's root filesystem mounted, and is root a moment later. On a Unix socket that means whoever can write /var/run/docker.sock. The daemon creates it as root:docker mode 660, so the question becomes who is in the docker group. OpenVAPT lists the members and treats the group as a list of administrators without sudo, logging or a password prompt (HOST-CTR-008).

Rootless mode moves the daemon itself into a user namespace owned by an ordinary account. A breakout then lands in that account, not in root. It does not suit every workload (some network and storage features differ), so HOST-CTR-009 reports it as information to feed a design decision rather than as a failure.

## Confinement, Limits and Identity Inside the Container

A container is ordinary processes with namespaces and cgroups around them, and three more layers that are easy to switch off. The default seccomp profile removes around 40 dangerous system calls; AppArmor or SELinux limits what the container's root may touch on the host. SecurityOpt entries such as seccomp=unconfined or apparmor=unconfined remove those layers for that container (HOST-CTR-011). Memory and PID limits (HOST-CTR-012) turn a fork bomb or a leak into one container's problem instead of the host's.

Finally, who runs inside matters. A process that is root in the container is root for every kernel bug, and a writable root filesystem lets an intruder install tools and persist. USER in the image and --read-only with a tmpfs for scratch space close both (HOST-CTR-013). The same questions apply to containerd and Podman. A containerd gRPC endpoint on TCP without TLS is the Docker API problem again. Podman's containers.conf sets the confinement defaults every container inherits (HOST-CTR-014).

## Trusting What You Pull

docker pull takes whatever the registry returns for a tag. Docker Content Trust (DOCKER_CONTENT_TRUST=1) makes the client verify a signature first, so a substituted image fails to pull instead of running as root on your host (HOST-CTR-015). Pair it with pinning images by digest in production and scanning images before they are pushed.

## In Summary

The Docker daemon sets the defaults every container inherits, and the socket is root. Bound logs, keep the userland proxy and ulimits deliberate, and restrict the socket and the docker group. Keep seccomp and AppArmor on and set memory and PID limits. Avoid root with a writable root filesystem, never expose a daemon over TCP without TLS, and verify what you pull.

## Apply It

- [HOST-CTR-005 Docker Container Logs Have No Size Limit](https://openvapt.com/checks/HOST-CTR-005.md) (low)
- [HOST-CTR-006 Docker Userland Proxy Enabled](https://openvapt.com/checks/HOST-CTR-006.md) (low)
- [HOST-CTR-007 Docker Default Ulimits Not Set](https://openvapt.com/checks/HOST-CTR-007.md) (low)
- [HOST-CTR-008 Docker Socket Accessible to Non-Administrators](https://openvapt.com/checks/HOST-CTR-008.md) (high)
- [HOST-CTR-009 Docker Not Running in Rootless Mode](https://openvapt.com/checks/HOST-CTR-009.md) (info)
- [HOST-CTR-010 Docker Daemon Allows Insecure Registries or Debug Features](https://openvapt.com/checks/HOST-CTR-010.md) (medium)
- [HOST-CTR-011 Seccomp or AppArmor Disabled for Containers](https://openvapt.com/checks/HOST-CTR-011.md) (high)
- [HOST-CTR-012 Containers Run Without Memory or Process Limits](https://openvapt.com/checks/HOST-CTR-012.md) (medium)
- [HOST-CTR-013 Containers Run as Root With a Writable Root Filesystem](https://openvapt.com/checks/HOST-CTR-013.md) (medium)
- [HOST-CTR-014 containerd Remote API or Podman Unconfined Defaults](https://openvapt.com/checks/HOST-CTR-014.md) (high)
- [HOST-CTR-015 Docker Content Trust Not Enabled](https://openvapt.com/checks/HOST-CTR-015.md) (low)

## Lab

1. `Run: openvapt demo --only HOST-CTR- --out a5 and sort the findings into daemon defaults, socket and group, per-container settings and image trust.`
2. `Compare the vulnerable and hardened daemon.json and hostconfig.json in internal/lab/fixtures_containers_apps.go line by line; for each difference, name the check that would notice it.`
3. `On a test machine you are allowed to assess: getent group docker, then stat -c '%a %U:%G %n' /var/run/docker.sock, and decide whether the group's members should instead use sudo docker.`

## Check Yourself

Answer keys are public for every channel (see Teaching With OpenVAPT).

1. A user is in the docker group but has no sudo rights. What can they do?
   - A. Only list containers
   - B. Anything root can do, by starting a container with the host filesystem mounted
   - C. Nothing until an administrator approves
   - D. Only start containers without network access
   - Answer: B. The Docker API is equivalent to root; group membership is unaudited root access.

2. Which setting removes Docker's default system call filter for one container?
   - A. --memory 512m
   - B. --security-opt seccomp=unconfined
   - C. --read-only
   - D. --cap-drop ALL
   - Answer: B. seccomp=unconfined disables the default profile that blocks dangerous system calls.

3. Why is a json-file log driver without max-size a problem?
   - A. Logs are encrypted and cannot be read
   - B. One container can fill the host's disk with its own logs
   - C. Docker refuses to start
   - D. The logs are sent to the internet
   - Answer: B. Unbounded logs are a denial of service against the host and every neighbour.

4. What does rootless mode change?
   - A. Containers can no longer use the network
   - B. The daemon and containers run inside a user namespace as an ordinary account, so a breakout does not land in root
   - C. Images are signed automatically
   - D. Containers run without any namespaces
   - Answer: B. Rootless mode limits the blast radius of a breakout; it does not sign images or remove networking.

5. What does userns-remap in daemon.json do?
   - A. Maps container root to an unprivileged host user
   - B. Renames containers
   - C. Turns off container networking
   - D. Makes images smaller
   - Answer: A. With remapping, root inside a container is an ordinary user on the host, so a breakout gains less.

## Key Terms

- Container: A group of processes isolated from the host and from each other by kernel namespaces, cgroups and capabilities.
- Namespace: A Linux kernel feature that gives processes their own view of resources such as process ids, network or mounts.
- Seccomp Profile: A list of system calls a process may make; a container runtime applies a default profile that blocks dangerous calls, and unconfined removes it.
- Rootless Mode: Running the container daemon and its containers inside a user namespace as an ordinary account, so a breakout does not land in root.
- Content Trust: Verifying a signature on an image before pulling or running it, so a substituted image is refused.
- Least Privilege: Giving every account and process only the access it needs, nothing more.

## References

- [NIST SP 800-190, Application Container Security Guide](https://csrc.nist.gov/pubs/sp/800/190/final)
- [Docker documentation, Docker Engine security](https://docs.docker.com/engine/security/)
- [Docker documentation, Rootless mode](https://docs.docker.com/engine/security/rootless/)
- [OWASP Docker Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)
- [CIS Docker Benchmark (recommendation numbers are cited in each check)](https://www.cisecurity.org/benchmark/docker)

Offline: `openvapt learn A5`.

---

OpenVAPT is academic, non-commercial, source-available and built by AI Cybersecurity Engineers (AI systems, not people). Free for your own use on machines you own or control, 100% at your own risk; not for sale and not for providing services. This page in HTML: https://openvapt.com/academy/a5.html. Part of OpenVAPT Academy. How to cite: https://openvapt.com/cite.html. Site summary for models: https://openvapt.com/llms.txt.
