Guide

Containers share the host kernel, so a kernel exploit can reach the host. That makes a container a convenience boundary, not a strong one. You defend it in layers. Each layer limits what an attacker can do after they get in.

Concept

Linux capabilities split root's powers into about 40 separate permissions, such as CAP_NET_ADMIN. A seccomp profile is a filter that blocks risky system calls. Docker applies both by default, and you can tighten them.

Walking through a hardened container

This command starts an image with most defences turned on.

Bash
docker run --read-only --tmpfs /tmp \
  --cap-drop=ALL --security-opt no-new-privileges \
  --user 10001 --memory 512m --cpus 0.5 \
  myorg/api@sha256:9f86d081884c7d65...

Each flag closes one door.

  • --read-only with --tmpfs /tmp: the root filesystem is read-only, and only /tmp is writable. An attacker cannot drop a binary, and an accidental write fails loudly.
  • --cap-drop=ALL: removes every capability. Most web services need none.
  • --security-opt no-new-privileges: a setuid binary cannot gain extra rights.
  • --user 10001: the process is not root.
  • --memory and --cpus: the limits from the cgroups page apply.
  • The image is pinned by digest, so the bytes are the ones you tested.

Layers of defence

  1. Minimal images. Every package is a potential CVE. A distroless image has no shell, so an attacker cannot simply run curl | sh.
  2. Image scanning and signing. Tools such as Trivy, Grype and Docker Scout compare packages against vulnerability databases. Fail CI on fixable critical CVEs, and sign images with cosign. The CI page covers the supply chain.
  3. Non-root user. Ideally user namespaces map container root to an unprivileged host UID.
  4. Read-only root filesystem. Mount a tmpfs only for paths that need writes.
  5. Dropped capabilities. Add back only what the service needs.
  6. No secrets in images. Inject runtime secrets from a manager, as covered in secrets and encryption. Use BuildKit secret mounts for build-time credentials.
  7. Never mount the Docker socket. Anyone who can reach it effectively has root on the host.

Common mistakes

  • Saying "it's in a container, so it's safe." Containers share the kernel. Do not run untrusted code on that assumption.
  • Putting a credential in a RUN step. Anyone who can pull the image can read the layer that holds it, even if a later step deleted it.
  • Mounting /var/run/docker.sock into an app. The app can start a privileged container on the host.

Check yourself

  1. Why is deleting a secret in a later layer not enough? Answer: The earlier layer still contains the secret, and anyone who pulls the image can read it.
  2. What does --cap-drop=ALL change? Answer: It removes all Linux capabilities, so even root inside the container has few privileges to use.

Key takeaways

  • Treat a container as a convenience boundary and defend it in layers.
  • Minimal images, scanning and signing shrink what can be attacked.
  • Non-root users, read-only filesystems and dropped capabilities limit what an attacker gets.
  • Secrets never belong in image layers, and the Docker socket never belongs in a container.

Further learning

The next page applies these ideas to a real image and shows how far a Dockerfile can shrink it: shrinking an image. For secrets handling, read secrets and encryption.