Git, CI/CD & GitOpsXLI · Runner SecurityPrivilegedContainers
Privileged containers and host mounts — what privileged means; the kernel surface
What you'll learn
- Explain the five mechanisms --privileged disables and what each one exposes
- Demonstrate that --privileged --cap-drop=ALL is a contradiction with capsh --print
- Identify the host-mount patterns that grant host-equivalent access: docker.sock, /, /proc, /sys, /var/lib/docker
- Choose between capability-scoped, device-scoped, and namespace-isolated configurations as alternatives
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
A privileged container is a single flag that turns off five
independent security mechanisms at once. The reason it is
dangerous is that four of them are not mentioned in the
flag’s name. The flag is --privileged; the mechanisms are
capabilities, devices, seccomp, AppArmor, and /proc and
/sys. The host-mount patterns (/var/run/docker.sock, /,
/proc, /sys, /var/lib/docker) reach the same place by
different routes. The defence is no privileged, no host
mounts, and explicit capability lists for every exception.
The five mechanisms --privileged disables
flowchart TB
subgraph N["Normal container"]
N1["Capabilities:\n14 in bounding set"]
N2["Device cgroup:\nshort allowlist"]
N3["Seccomp:\ndefault profile, ~44 syscalls blocked"]
N4["AppArmor/SELinux:\ndocker-default / container_t"]
N5["/proc, /sys:\n12 masked paths, 5 read-only paths"]
end
subgraph P["With --privileged"]
P1["Capabilities:\nevery kernel capability"]
P2["Device cgroup:\nevery device on the host, rwm"]
P3["Seccomp:\nunconfined"]
P4["AppArmor/SELinux:\nunconfined"]
P5["/proc, /sys:\nmasks removed, cgroup mounts writable"]
end
N --> P
- Capabilities. ~40 capabilities; each is root-
equivalent (
CAP_SYS_ADMIN,CAP_NET_ADMIN,CAP_SYS_PTRACE). Normal: 14 in bounding set;--privileged: full set. - Device cgroup. Controls which device nodes
(
/dev/null,/dev/sda,/dev/mem) a process can access. Normal: short allowlist;--privileged: every device on the host withrwm. - Seccomp. Restricts syscalls. Normal: default
profile blocks ~44 syscalls;
--privileged:unconfined. - AppArmor / SELinux. Mandatory access control.
Normal:
docker-default/container_t.--privileged:unconfined. /procand/sys. Proc and sys expose kernel state. Normal: 12 masked paths, 5 read-only paths, sysfs read-only.--privileged: masks removed, cgroup mounts writable.
Any one of the five is a plausible route out of the container. Together, a privileged container is a root shell on the host with a couple of extra steps.
Proving the surface with capsh --print
The command that demonstrates the surface from inside a container:
# Inside a normal container
capsh --print
# Current: =cap_chown,...
# Bounding set =cap_chown,...
# Ambient set =
# Inside a privileged container
capsh --print
# Current: =cap_chown,...,cap_sys_admin,...,cap_sys_resource,...+ep
# Bounding set =cap_chown,...,cap_sys_admin,...,cap_sys_resource,...
# Note the +ep at the end: Effective and Permitted
The +ep suffix means the capabilities are both Effective
and Permitted. A normal container shows the 14 capabilities
without +ep; a privileged container shows every
capability with +ep. The capsh --print output is the
proof; the auditor runs the command inside the container
and compares it to the expected normal-container list.
Host mounts equivalent to --privileged
The --privileged flag is one path; host mounts are
another. The five mounts to audit:
flowchart LR
subgraph M["Host mounts that grant host root"]
M1["/var/run/docker.sock"]
M2["/ (host root)"]
M3["/proc (host proc)"]
M4["/sys (host sysfs)"]
M5["/var/lib/docker"]
end
M1 --> E["Equivalent to --privileged"]
M2 --> E
M3 --> E
M4 --> E
M5 --> E
/var/run/docker.sock— covered in XLI-04. Two hops./(host root). Read/etc/shadow, write/root/.ssh/authorized_keys, replace/etc/passwd, install a systemd unit. One hop./proc(host proc). Read/proc/1/environ(host PID 1 environment, often contains secrets). WithCAP_SYS_PTRACE,ptracehost processes and inject code./sys(host sysfs). Write to/proc/sys/kernel/*tunables; modify kernel security policy from inside./var/lib/docker. Daemon data directory: every container’s filesystem layers, image manifests, running containers’ volumes.
The audit rule is the same: any host mount is a finding that requires justification.
Capability-scoped alternatives
A workflow that needs a single capability gets the capability explicitly, not the full set:
# Add a single capability; drop everything else
docker run --cap-drop=ALL --cap-add=NET_ADMIN ...
# NOT this:
docker run --privileged --cap-add=NET_ADMIN ...
The capability list is the contract; the audit verifies the
contract. A container with --cap-drop=ALL and one explicit
--cap-add has the capability it needs and no others.
The same pattern applies to devices:
# Allow only /dev/null and /dev/zero
docker run --device=/dev/null --device=/dev/zero ...
For workflows that need a namespace outside the container
(for example, the host’s PID namespace to debug a host
process), the namespace is granted explicitly via
--pid=host:
# Grant host PID namespace, but NOT privileged
docker run --pid=host --cap-drop=ALL --cap-add=SYS_PTRACE ...
The container shares the host’s PID namespace and can
ptrace host processes; the container cannot mount the
host filesystem, cannot read the host’s block devices,
cannot modify kernel tunables.
Production discipline
- No
--privilegedon runners. Every workflow that uses Docker gets a capability-scoped configuration with--cap-drop=ALLand explicit--cap-add. - No host mounts on runners. The audit runs
mount | grep -E 'docker.sock|^/proc|^/sys|/var/lib/docker'quarterly. Findings are remediated. --privileged --cap-drop=ALLis a finding, not a configuration. The workflow must be reconfigured before merge.- Capability list is in the workflow file. Every
--cap-addand every--deviceis documented with a justification. The auditor reads the file and verifies the list.
Cross-course references
- Docker for Production Sysadmins — Part XXXV-05 (Privileged containers) covers the capability and device surface in detail.
- Docker for Production Sysadmins — Part XXXV-04 (Docker socket security) covers the most common host-mount exposure.
- Git, CI/CD & GitOps — Part XLI-04 (The Docker socket risk) covers the socket-specific escalation path.
- Linux for Production Sysadmins — Part XII (CapabilitiesAndNamespaces) covers the kernel-side primitives.
Quiz
Knowledge check · 4 questions
Q1. Which of the following is the most accurate description of what `--privileged` does to a container?
Q2. A container run with `--privileged --cap-drop=ALL --read-only --security-opt no-new-privileges=true` is meaningfully less privileged than a plain `--privileged` container because the cap-drop and read-only flags are honoured.
Q3. List the five host mounts that grant host-equivalent access, and the command you would run to detect each on a runner host.
Q4. Diagnose the capability surface of a runner container with capsh --print and recommend the capability-scoped alternative.
An engineer runs `capsh --print` inside a runner container started with `--privileged` and gets a full bounding set with the `+ep` suffix on every capability, including CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_NET_ADMIN, CAP_DAC_OVERRIDE. The workflow only needs to bind to a port below 1024 for a test server (CAP_NET_BIND_SERVICE). The runner hosts are persistent.
Passing score: 75%. Answers are checked in this browser.