Docker Security

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Containers isolate well by default, but they share a kernel with the host and they are managed by a daemon that runs as root. Security in Docker is therefore about three things: who can talk to the daemon, how much of the kernel a container can reach, and what code ends up inside the image. Chapter 10 of Docker: Up & Running and chapter 11 of Learning Docker list the essentials — UID 0 inside the container, privileged containers, SELinux/AppArmor, dropping capabilities, read-only filesystems — and all of that still applies. This page restates it against Docker Engine 29.8.x with the controls that have appeared since: rootless mode, user namespaces, seccomp profiles, no-new-privileges, Enhanced Container Isolation and supply-chain tooling.

Threat model

  • The daemon is root. dockerd runs as root (except in rootless mode) and performs every privileged operation on behalf of its clients.

  • The socket is root. Anyone who can reach /var/run/docker.sock or a remote API endpoint can start a container that mounts the host filesystem — the docker group is root-equivalent (see Docker Architecture and the OCI).

  • Images are code. Pulling and running an image runs someone else’s software, with whatever it contains.

  • The kernel is shared. A kernel vulnerability reachable from a container is a host vulnerability; the defaults below exist to shrink that reachable surface.

Isolation layers around a container process: namespaces and cgroups and capabilities and seccomp and AppArmor or SELinux and an optional user namespace and rootless daemon

Derived from Docker Engine security.

Protecting the daemon

  • Never expose the API on plain TCP (-H tcp://0.0.0.0:2375). For remote access use an SSH context (docker context create remote --docker host=ssh://admin@host) or mutual TLS on port 2376 with client certificates.

  • Keep the docker group to people who would get sudo anyway.

  • Mount the socket into a container only for tools you trust with root on the host (CI agents, Testcontainers' Ryuk); consider a socket proxy that allows only the API calls a tool needs.

Rootless mode

Rootless mode runs the daemon and the containers as an unprivileged user inside a user namespace, so a daemon or container escape lands in an ordinary user account:

dockerd-rootless-setuptool.sh install          # as the unprivileged user; ships with the docker-ce-rootless-extras package
systemctl --user enable --now docker
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker info --format '{{.SecurityOptions}}'    # includes name=rootless

Trade-offs: ports below 1024 need extra configuration, some storage drivers and network features are restricted, and --privileged cannot grant more than the user has. Derived from Rootless mode.

User-namespace remapping

With "userns-remap": "default" in daemon.json, the (still rootful) daemon maps UID 0 in every container to an unprivileged subordinate UID range on the host (dockremap). A process running as root in a container is then nobody special on the host. It is daemon-wide, complicates bind-mount permissions, and on Engine 29 disables the containerd image store. Derived from Isolate containers with a user namespace.

Hardening containers at run time

Start from the principle of least privilege and add back only what the application needs:

docker run -d --name orders \
  --user 10001:10001 \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --memory 768m --cpus 1.5 --pids-limit 200 \
  -p 127.0.0.1:8080:8080 \
  ghcr.io/myorg/orders-service:1.4.0
Control What it does

Non-root user (USER in the image, --user)

The process has no root privileges even inside its namespaces. Set it in the Dockerfile (see USER).

Capabilities (--cap-drop ALL, --cap-add NET_BIND_SERVICE)

Docker already drops most of root’s ~40 capabilities; dropping all and adding back one or two removes the rest. A JVM listening on 8080 needs none.

Seccomp

The default profile blocks around 44 of the 300+ system calls (such as kexec_load, mount, bpf for unprivileged use). Keep it; supply a custom profile with --security-opt seccomp=profile.json only to tighten further. seccomp=unconfined disables it.

AppArmor / SELinux

Mandatory access control. Docker applies the docker-default AppArmor profile on Ubuntu/Debian; on RHEL/Fedora SELinux labels confine containers (:z/:Z on bind mounts).

no-new-privileges

Prevents gaining privileges through setuid binaries or file capabilities after start.

--read-only + --tmpfs

An immutable root filesystem with explicit writable scratch space.

Resource limits

--memory, --cpus, --pids-limit stop one container from starving the host (see Resources, Logging and Monitoring).

No --privileged

--privileged gives all capabilities, all devices, and disables seccomp and AppArmor: effectively root on the host. Grant a single device with --device instead.

Derived from Docker Engine security, Seccomp security profiles and AppArmor security profiles. The page Docker security non-events lists kernel vulnerabilities these defaults mitigated without anyone having to patch in time.

Secrets at build time and at run time

flowchart LR subgraph Build["Build time"] s1["settings.xml / npm token
on the build machine or CI secret"] -->|docker build --secret| m1["RUN --mount=type=secret
file for one step only"] m1 --> l1["Image layers: secret absent"] bad1["ARG / ENV / COPY of the secret"] -.->|never| l2["Leaked into layers and history"] end subgraph Run["Run time"] s2["Secret store / CI secret / file"] -->|Compose secrets or orchestrator| m2["/run/secrets/name
read-only file"] m2 --> app["Application reads the file"] bad2["docker run -e PASSWORD=..."] -.->|avoid| l3["Visible in docker inspect
and child process env"] end

Build-time secrets are mounted with RUN --mount=type=secret (see Image Best Practices); run-time secrets are files provided by Compose (secrets:), Swarm or Kubernetes, not environment variables. Derived from Build secrets and Manage secrets securely in Docker Compose.

Image hygiene

Run images you can trace: pinned tags and digests, minimal or hardened base images, regular rebuilds, Docker Scout scanning, SBOM and provenance attestations and signatures — all covered on Image Best Practices and Supply-Chain Security. Docker Content Trust, which Learning Docker recommends for signature verification, is historical: Engine 29 removed it from the CLI; use Sigstore or Notation signatures verified at deploy time.

Docker Desktop: Enhanced Container Isolation

For organisations that manage Docker Desktop centrally, Enhanced Container Isolation runs every container in a Linux user namespace inside the Desktop VM and prevents containers from modifying the VM or reaching the Docker Engine socket — even --privileged ones. It is part of Docker Business’s Hardened Docker Desktop and is named here only; see Enhanced Container Isolation.

Consolidated checklist

The security recommendations from both books, merged and brought up to date, with the official page for each row:

Recommendation From Official page

Run processes as a non-root user

Both books

Best practices: USER

Never run --privileged; grant single devices instead

Both books

Runtime privilege and Linux capabilities

Drop capabilities you do not need

Learning Docker ch. 11

Linux kernel capabilities

Keep SELinux or AppArmor enabled

Both books

AppArmor security profiles

Keep the default seccomp profile

New since 2015

Seccomp security profiles

Read-only root filesystem, explicit tmpfs

Learning Docker ch. 11

--read-only

Limit memory, CPU and PIDs

Both books

Resource constraints

Protect the daemon socket; no plain-TCP API

Docker: Up & Running ch. 10

Protect the Docker daemon socket

Rootless daemon or user-namespace remapping

New since 2015

Rootless mode

no-new-privileges

New since 2015

--security-opt

Secrets as files, never in ENV/ARG

New since 2015

Build secrets

Trusted, minimal, scanned base images

Learning Docker ch. 11

Docker Scout, Docker Hardened Images

Verify image signatures (Content Trust in 2015)

Learning Docker ch. 11 — superseded

Retired features (Content Trust retirement); Sigstore/Notation

The CIS Docker Benchmark turns the same controls into an auditable list and is the usual basis for compliance checks.