Capabilities Dropping in Docker
How Docker uses Linux capabilities to break the all-or-nothing power of root into dozens of separate privileges — and why removing the ones a container doesn't need is one of the highest-leverage security habits available.
Ask most engineers what “root” means on Linux and they will describe a single, all-powerful account that can do absolutely anything on the system — change any file, kill any process, reconfigure the network, load kernel modules, override every permission check. For decades that was true, and it created an uncomfortable reality for containers: a process running as root inside a container, even though it is walled off by namespaces from the rest of the host, still carries that same terrifying all-or-nothing power within its own container boundary, and history has shown that boundary is not always perfectly sealed. Linux capabilities exist to fix exactly this problem, by splitting root’s monolithic power into roughly forty separate, independently grantable privileges. Capabilities dropping is the practice of deliberately stripping away every one of those privileges a container does not genuinely need, so that even a process running as root inside the container is left with only a fraction of root’s original power. This guide explains what capabilities actually are, how Docker manages them, and how to drop them effectively without breaking legitimate workloads.
Chapter One
ACore Concepts
Understanding capabilities dropping starts with understanding what a Linux capability is, and why the kernel bothered inventing them in the first place.
What a Linux capability actually is
A capability is a single, named unit of privilege that the Linux kernel checks independently before allowing a specific class of operation. Instead of a binary “are you root or not” question, the kernel asks a much more specific question for each sensitive action: does this process hold the particular capability required for this particular operation? Binding to a network port below 1024, for example, requires the CAP_NET_BIND_SERVICE capability. Changing file ownership requires CAP_CHOWN. Loading a kernel module requires CAP_SYS_MODULE. A process can hold any combination of these roughly forty capabilities, meaning it can be granted exactly the narrow slice of root’s power it actually needs, and nothing more.
Think of traditional root access like a single master key that opens every door in a large office building — the server room, the finance office, the roof access hatch, everything. Capabilities are like replacing that one master key with a keyring where each key opens exactly one door. A cleaning contractor might be issued the keys to every office but deliberately not the key to the server room or the roof — they can do their job completely, while a stolen or misused keycard still can’t compromise the areas they never needed.
Why containers make this especially important
On a traditional server, running a process as root is a well-understood, if risky, decision made once for a whole machine. In a container environment, dozens or hundraden of independently authored images — many pulled from public registries whose full contents were never audited line by line — frequently run as root inside their own containers by default, because it is the path of least resistance for image authors. Namespaces isolate a root process inside a container from directly seeing the host’s files or processes under normal conditions, but that isolation has historically had edge cases and kernel vulnerabilities that allowed a root-privileged container process to escalate beyond its boundary. Capabilities dropping adds a second, independent layer of defense: even if a container process is technically “root” and even if a namespace boundary is somehow bypassed, a process stripped of most capabilities simply lacks the kernel-level permission to perform the most dangerous operations that would make an escape or a host compromise possible.
Capabilities and user IDs are two separate, complementary security dimensions. Running a container as a non-root user is one valuable hardening step; dropping capabilities is a different, additional step that limits what even a root-privileged process inside the container can do. Using both together — a non-root user with a minimal capability set — is stronger than either one alone.
Docker’s default capability set
Rather than granting a new container every capability the kernel supports, Docker starts every container off with a curated default subset — historically around fourteen capabilities, covering common needs like changing file ownership, binding privileged ports, and sending signals to other processes in the same container. This default set is already a meaningful reduction from full root privilege, but it still includes several capabilities that the overwhelming majority of ordinary application containers never actually use, which is precisely the gap capabilities dropping is designed to close.
Chapter Two
BArchitecture & Components
Capabilities are enforced by a specific kernel subsystem working alongside — not instead of — the container isolation mechanisms covered elsewhere in Docker’s security model.
flowchart TB
subgraph KERNEL["Linux Kernel"]
CHK["Privileged Syscall
(e.g. mount, kill, chown)"]
CAPCHK["Capability Check"]
ALLOW["Operation Permitted"]
DENY["EPERM Returned"]
end
PROC["Container Process
(capability set attached)"]
PROC --> CHK
CHK --> CAPCHK
CAPCHK -->|has required capability| ALLOW
CAPCHK -->|missing capability| DENY
Capability Sets
Every process carries several distinct capability sets — permitted, effective, inheritable, bounding, and ambient — that together determine exactly which privileges it currently holds and which it could potentially gain.
runc / containerd
The low-level container runtime that actually configures a new container process’s capability sets at creation time, based on the values Docker’s daemon passes down to it.
Default Capability List
The daemon maintains the curated default set of roughly fourteen capabilities granted to any container that does not explicitly customize its capability configuration.
–cap-drop / –cap-add
The command-line interface Docker exposes for removing specific capabilities from the default set, adding specific ones back, or dropping every capability at once as a starting point.
Kernel Syscall Layer
The actual check happens deep inside the kernel at the moment a privileged system call is made, independent of any container-aware logic — capabilities are a general Linux feature Docker configures, not something Docker enforces itself.
seccomp & AppArmor / SELinux
Capabilities restrict which broad privileges a process has; seccomp filters restrict which specific system calls it may invoke at all; mandatory access control systems like AppArmor add a further, independent layer — the three work together, not as substitutes for each other.
Where capabilities sit relative to namespaces
It helps to be clear about the division of labor. Namespaces (covered in topics like network namespace sharing) control what a process can see — which network interfaces, which process list, which mounted filesystem. Capabilities control what a process with root privilege is allowed to do once it can see something. A process might be namespace-isolated from the host’s real network interfaces entirely, and separately also lack the CAP_NET_ADMIN capability needed to reconfigure networking even within its own namespace. These two mechanisms are complementary layers of the same overall container security model, not competing approaches.
Chapter Three
CInternal Working
A look at exactly how the kernel decides, at the moment a privileged operation is attempted, whether a process is allowed to proceed.
The moment of the check
Every time a process attempts an operation the kernel considers privileged — changing a file’s owner, adjusting system time, binding a low-numbered network port, mounting a filesystem — the kernel’s system call handler for that operation checks whether the calling process’s effective capability set includes the specific capability that operation requires. If the capability is present, the operation proceeds exactly as it would for unrestricted root. If it is absent, the kernel immediately rejects the call with a permission-denied error, regardless of whether the process’s user ID is zero (root) or not. This is the crucial mechanical fact that makes capabilities dropping meaningful: user ID and capability possession are checked separately, and a process can be user ID zero while still being denied an operation purely because it lacks the matching capability.
How Docker builds a container’s capability set at startup
When a container starts, Docker’s daemon computes the container’s final capability set by starting from its internal default list, then removing any capabilities named in a drop instruction, then adding back any capabilities named in an add instruction, and finally passing that resolved list down to the container runtime, which configures it into the new process’s permitted, effective, and bounding sets before the container’s first command ever executes. Crucially, the bounding set — the absolute ceiling on which capabilities a process or any of its descendants can ever hold, even if it tries to re-acquire them later — is set at this same moment, which is what prevents a dropped capability from being silently regained later in the container’s life through ordinary means.
sequenceDiagram
participant U as docker run --cap-drop ALL --cap-add NET_BIND_SERVICE
participant D as Docker Daemon
participant R as Runtime (runc)
participant P as Container Process
U->>D: requested drop/add list
D->>D: default set - drops + adds = final set
D->>R: final capability set
R->>P: configure permitted/effective/bounding sets
Note over P: process starts already
holding only NET_BIND_SERVICE
Why the bounding set matters more than it first appears
Without the bounding set acting as a hard ceiling, a sufficiently clever process could potentially find ways to re-acquire a capability that was only removed from its immediate effective set — for instance by re-executing a file with certain permission bits set, a classic privilege-escalation technique on traditional Linux systems. Because Docker configures the bounding set itself at container creation, a capability that has been dropped is not just temporarily inactive; it becomes genuinely unreachable for the remaining lifetime of that container, closing off this entire category of escalation regardless of what the process inside subsequently attempts.
Chapter Four
DData Flow & Lifecycle
Tracing the lifecycle of a container’s capability configuration from the moment it is specified to the moment it governs an actual system call.
Configuration Specified
An operator or a Compose file specifies which capabilities to drop, which to add back, or requests dropping every capability as the starting baseline.
Final Set Resolved
The Docker daemon computes the resulting capability list by applying the requested drops and adds on top of its internal default set.
Runtime Configures the Process
The container runtime configures the new process’s permitted, effective, and bounding capability sets before the container’s entrypoint command begins executing.
Steady-State Enforcement
For the remainder of the container’s life, every privileged system call made by any process inside it is checked against this fixed capability set at the moment it is attempted.
Child Processes Inherit the Ceiling
Any new process spawned inside the container inherits capabilities governed by the same bounding set, meaning it can never exceed what the container was originally configured with.
Container Exits
The capability configuration, like the rest of the container’s runtime state, ceases to exist along with the container; a new container started from the same image begins the process again from its configured defaults.
What happens when a dropped capability is needed
If a process inside a container attempts an operation requiring a capability that has been dropped, the kernel returns a standard permission-denied error at the exact point of that system call — the same error an ordinary, unprivileged Linux process would receive attempting the same operation. This is not a Docker-specific error message; it surfaces exactly as it would on any Linux system, which means troubleshooting a “mysteriously failing” container after capabilities have been tightened often means recognizing a permission-denied failure as a capability issue rather than a filesystem or application-configuration issue.
Chapter Five
EAdvantages, Disadvantages & Trade-offs
Capabilities dropping is close to a pure security win, but it is not entirely free, and understanding its limits keeps expectations realistic.
Advantages
- Meaningfully shrinks what a compromised or malicious container process can do, even while running as root inside its own namespace
- Closes off entire categories of privilege-escalation and container-escape techniques that rely on specific dangerous capabilities
- Costs no runtime performance — capability checks are a normal, lightweight part of every Linux system call
- Works alongside, and strengthens, every other container hardening measure rather than replacing any of them
- Can be applied incrementally, starting from the default set and dropping capabilities one at a time as they are verified unnecessary
Disadvantages
- Determining exactly which capabilities a given application genuinely needs requires careful testing, not guesswork
- Overly aggressive dropping can cause confusing, hard-to-diagnose permission failures deep inside third-party application code
- Does not protect against vulnerabilities that do not require any capability at all, such as many application-layer logic flaws
- Some legacy or poorly designed images assume full default capabilities and may not function correctly once capabilities are restricted
- Provides no protection against a kernel vulnerability that bypasses the capability check mechanism itself
Defense in depth, not a silver bullet
Capabilities dropping should be understood as one strong layer within a broader defense-in-depth strategy, not a complete container security solution on its own. It meaningfully reduces the blast radius of a compromised container, particularly against known classes of container-escape techniques that rely on specific dangerous capabilities like CAP_SYS_ADMIN or CAP_SYS_MODULE, but it does nothing to prevent, for example, an application-level SQL injection vulnerability or a leaked credential from being misused within whatever access that credential legitimately grants. The honest framing is that capabilities dropping shrinks the kernel-level attack surface available to an attacker who has already gained code execution inside a container — a genuinely valuable and cheap improvement, but only one piece of a complete security posture.
The strongest and most common practical pattern is to drop every capability as a starting point and then explicitly add back only the small handful an application has been verified to actually require, rather than starting from Docker’s default set and trying to guess which of the fourteen default capabilities are safe to remove. This inverts the default from “permissive unless proven dangerous” to “restrictive unless proven necessary,” which is the more defensible security posture.
Chapter Six
FSecurity
Because capabilities dropping is fundamentally a security control, this chapter looks specifically at the dangerous capabilities that matter most and the attack classes they close off.
The most dangerous capabilities to know by name
| Capability | What It Grants | Why It’s High-Risk in a Container |
|---|---|---|
CAP_SYS_ADMIN | A broad grab-bag of administrative operations, including many mount-related actions | Frequently the single capability most associated with known container-escape techniques; considered the closest thing to “root-lite” |
CAP_SYS_MODULE | Load and unload kernel modules | Allows loading arbitrary code directly into the host kernel, a near-total compromise if reachable from inside a container |
CAP_SYS_PTRACE | Inspect and control other processes’ memory and execution | Can allow a container process to read secrets out of another process’s memory, including processes outside expected boundaries |
CAP_NET_RAW | Create raw network sockets | Enables packet crafting and certain network-based attacks like spoofing that ordinary sockets do not permit |
CAP_DAC_OVERRIDE | Bypass normal file read/write/execute permission checks | Defeats the purpose of ordinary file permission restrictions within the container’s filesystem |
If CAP_SYS_ADMIN is the master key that opens almost every door, dropping it before handing an untrusted contractor the rest of the keyring is like removing the one key that happens to also open the building’s electrical control room — the contractor can still do their actual job perfectly well with the remaining keys, but the single most consequential door is simply never available to them, regardless of any other mistake made elsewhere in the security setup.
How dropping interacts with running as non-root
The strongest, most widely recommended container hardening combination pairs a non-root user with an aggressively minimized capability set — dropping everything and adding back only what is verified necessary. These two measures address different attacker scenarios: running as non-root limits damage even if every capability were somehow still present, because many privileged operations additionally require an effective user ID of zero on top of the relevant capability; minimizing capabilities limits damage even in the rarer case where a process does hold or briefly regains elevated user privilege. Neither substitutes fully for the other, which is why security guidance consistently recommends applying both together rather than treating them as alternative options.
Context
A security review found that the majority of internally built container images ran with Docker’s full default capability set despite very few of them ever exercising most of those default privileges.
Decision
All internally maintained base images were updated to drop every capability by default, with individual teams required to explicitly document and add back any specific capability their application genuinely needed.
Consequence
A handful of legacy applications required a short remediation effort to identify and re-add the one or two capabilities they actually relied on, but the organization’s baseline container attack surface was reduced fleet-wide with no ongoing performance cost.
Chapter Seven
GMonitoring, Logging & Metrics
Because capability checks happen deep in the kernel, visibility into how they are behaving requires purpose-built observability, not general container metrics.
Detecting denied capability attempts
When a process attempts an operation requiring a capability it does not have, the kernel’s audit subsystem, where enabled, can record the specific denied attempt, including which capability was missing and which process made the request. This is the single most useful signal for two very different purposes: catching a legitimate application that needs a capability it wasn’t granted, and detecting a compromised or malicious process probing for capabilities it should never have needed in the first place, both of which produce the same underlying kernel-level denial event but warrant very different responses.
Occasional Denials During Rollout
A short-lived spike in capability denial events immediately after tightening a container’s capability set usually indicates a legitimate, previously-unnoticed dependency that needs to be added back explicitly.
Denials for High-Risk Capabilities in Steady State
Repeated attempts to use a capability like CAP_SYS_ADMIN or CAP_SYS_MODULE from a container that has been stable in production for a long time is a stronger indicator worth treating as a potential security event rather than routine noise.
Zero Denials, Zero Excess Capabilities
A well-tuned container in a mature state shows no capability denials at all, because its granted set has already been trimmed to exactly what the application uses.
Auditing what capabilities a running container actually holds
Beyond watching for denials, periodic auditing of the capability set each running container is actually configured with — comparing it against the organization’s documented minimal-necessary set for that image — catches configuration drift, where a container is redeployed at some point with a looser capability configuration than intended, whether through a copy-pasted deployment manifest or a well-meaning but overly broad troubleshooting change that was never reverted.
Because a container with excess capabilities usually runs perfectly normally — the extra capabilities simply sit unused — configuration drift toward a looser capability set produces no functional symptom at all, only a security regression. Regular auditing, not just monitoring for failures, is necessary to catch this class of problem.
Chapter Eight
HDesign Patterns & Anti-patterns
Recurring approaches that make capability management sustainable across many images and teams, and the recurring mistakes that erode its value.
Drop-All-Add-Back Baseline
Every image starts from zero capabilities, with each one added back individually and documented against the specific feature that requires it, making the capability list a form of living security documentation.
Privileged Helper, Unprivileged Application
A short-lived, narrowly scoped helper process or init container performs the one operation genuinely requiring an elevated capability (such as binding a privileged port), then the main long-running application process continues with that capability dropped entirely.
Per-Image Capability Manifest
Teams maintain an explicit, reviewed list of exactly which capabilities each image requires and why, checked as part of code review whenever a new capability is requested.
Adding Capabilities to Silence an Error
Adding back a capability purely because an application throws a permission error, without first diagnosing whether the underlying operation is actually necessary or could be redesigned to avoid needing it at all.
Copying a Broad Capability Set Across Unrelated Images
Reusing one image’s capability configuration for a different application with entirely different needs, granting privileges the new application never actually exercises.
Pattern in practice: the privileged-port problem
A common real-world case is a web application that wants to bind directly to port 80, which ordinarily requires the CAP_NET_BIND_SERVICE capability. Rather than running the entire application with a broad default capability set just to satisfy this one requirement, the recommended pattern is to drop every capability except that single one, letting the application bind its privileged port while remaining otherwise as unprivileged as an ordinary non-root process for everything else it does.
Concrete guidance for rolling out capability restrictions without breaking production workloads along the way.
Test in a staging environment first
Roll out a tightened capability set in a non-production environment under realistic load before applying it to production, since some capability dependencies only surface under specific, infrequent code paths.
Add back capabilities one at a time
When troubleshooting a permission failure after dropping all capabilities, add back the single most likely candidate rather than restoring several at once, to build an accurate, minimal picture of what the application truly needs.
Document why each retained capability exists
Record the specific feature or code path that requires each capability an image keeps, so future maintainers can evaluate whether it is still necessary rather than treating it as untouchable.
Reaching for privileged mode as a shortcut
Running a container in fully privileged mode — which effectively grants every capability plus additional device access — to resolve a single permission error is a drastic overcorrection almost never actually required.
Assuming third-party images are already minimal
Many popular public images run with Docker’s full default capability set out of the box; explicitly reviewing and tightening capabilities for third-party images is just as important as for internally built ones.
Treating capability review as a one-time exercise
Application code changes over time, and a capability that was genuinely required at one point may no longer be needed after a refactor, making periodic re-review worthwhile.
Chapter Ten
JReal-World & Industry Examples
Capabilities dropping is a foundational recommendation across nearly every major container security guideline and platform available today.
CIS Docker Benchmark
Widely referenced security benchmarks for container deployment explicitly recommend dropping all default capabilities and adding back only what is required, treating it as a baseline hardening expectation rather than an advanced technique.
Kubernetes Security Contexts
Kubernetes exposes its own capability drop and add controls directly through pod and container security context specifications, applying the same underlying Linux capability mechanism this article describes at the orchestration layer.
Regulated Industry Deployments
Organizations in finance, healthcare, and other regulated sectors frequently mandate minimal capability sets as part of formal container hardening standards required for audit and compliance purposes.
Hardened Base Image Projects
Security-focused base image projects have gained popularity specifically because they ship with minimized default capabilities and non-root users out of the box, reducing the hardening work required of every downstream image that builds on them.
Chapter Eleven
KFrequently Asked Questions
Chapter Twelve
LSummary and Key Takeaways
Capabilities dropping takes root’s historically monolithic power and cuts it down to exactly what each container genuinely needs.
Key Takeaways
- Linux capabilities split root’s traditional all-or-nothing privilege into roughly forty independently grantable units, checked separately by the kernel for each privileged operation.
- Docker grants every container a curated default set of around fourteen capabilities, which already exceeds what most ordinary application containers actually use.
- Dropped capabilities are removed from the container’s bounding set at creation time, making the restriction genuinely permanent for that container’s lifetime, not merely a temporary suppression.
- The strongest and most widely recommended pattern is dropping every capability and explicitly adding back only the ones a specific application has been verified to require.
- Capabilities dropping is one layer of defense-in-depth — it shrinks the damage a compromised container process can do, but does not replace non-root users, seccomp filtering, or application-level security.
- This practice is a baseline recommendation across major container security benchmarks and is directly exposed by orchestration platforms like Kubernetes, making it one of the highest-leverage, lowest-cost hardening steps available to any team running containers.