Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLI · Runner SecurityPrivilegedContainers

Privileged containers and host mounts — what privileged means; the kernel surface

Advanced⏱ ~24 mingit

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

Not yet marked complete on this device.

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 with rwm.
  • Seccomp. Restricts syscalls. Normal: default profile blocks ~44 syscalls; --privileged: unconfined.
  • AppArmor / SELinux. Mandatory access control. Normal: docker-default / container_t. --privileged: unconfined.
  • /proc and /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). With CAP_SYS_PTRACE, ptrace host 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

  1. No --privileged on runners. Every workflow that uses Docker gets a capability-scoped configuration with --cap-drop=ALL and explicit --cap-add.
  2. No host mounts on runners. The audit runs mount | grep -E 'docker.sock|^/proc|^/sys|/var/lib/docker' quarterly. Findings are remediated.
  3. --privileged --cap-drop=ALL is a finding, not a configuration. The workflow must be reconfigured before merge.
  4. Capability list is in the workflow file. Every --cap-add and every --device is 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

  1. Q1. Which of the following is the most accurate description of what `--privileged` does to a container?

  2. 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.

  3. Q3. List the five host mounts that grant host-equivalent access, and the command you would run to detect each on a runner host.

  4. 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.