Kubernetes securityContext capabilities let you control which Linux kernel capabilities a container process holds, separately from whether it runs as root — and the safe default is to drop all of them and add back only the specific ones a container actually needs, rather than accepting the container runtime's default capability set, which grants more than almost any application requires.
What are Linux capabilities, and why does Kubernetes expose them here?
Linux capabilities split up the power traditionally reserved for the root user into discrete, individually grantable privileges — NET_BIND_SERVICE for binding to ports below 1024, CHOWN for changing file ownership, SYS_ADMIN for a broad and dangerous grab-bag of administrative operations. Docker and containerd grant a default set of roughly a dozen capabilities to every container unless told otherwise, which is more privilege than a typical web application, API service, or batch job ever needs. Kubernetes surfaces this control through securityContext.capabilities on a pod or container spec specifically so cluster operators can tighten that default down per workload instead of accepting a one-size-fits-all privilege level across every container in the cluster.
How does "drop" differ from "add" in practice?
drop removes capabilities from the container's set, and add grants specific ones back — the field structure is capabilities: { drop: [...], add: [...] } under securityContext. The strongest posture is drop: ["ALL"], which strips every capability the runtime would otherwise grant, followed by an explicit add list containing only what that specific workload has verified it needs. This is the inverse of the common but weaker pattern of adding a couple of high-risk capabilities on top of the runtime default set without ever auditing what's already implicitly present — a pattern that tends to accumulate unnecessary privilege over time as engineers copy an existing manifest without re-evaluating what it actually grants.
What does runAsUser have to do with capabilities?
runAsUser (Kubernetes runAsUser) sets the UID the container process runs as, and it's a related but distinct control from capabilities — a non-root UID with no capabilities is a much stronger posture than a non-root UID with SYS_ADMIN still attached, or a root UID with all capabilities dropped except one narrowly scoped one. In practice, hardened pod specs combine both: runAsUser set to a non-zero UID (or runAsNonRoot: true enforced) and capabilities.drop: ["ALL"], so a compromised process has neither the UID-based root privilege nor the kernel-capability privilege that would let it do meaningful damage even if the application itself is exploited. Treat these as complementary layers, not substitutes for each other — dropping capabilities on a process still running as UID 0 leaves more residual privilege than most teams realize.
What are the capabilities workloads most commonly need to add back?
NET_BIND_SERVICE is the most frequent addition, needed by any process that binds directly to a privileged port (80, 443) without a Kubernetes Service or sidecar proxy handling that translation — modern practice increasingly avoids needing this at all by binding to an unprivileged port and letting the Service or Ingress handle the low-port mapping instead. CHOWN and SETUID/SETGID occasionally show up for processes that manage file ownership or drop privileges internally after startup. NET_RAW is needed by tools that construct raw sockets (some networking diagnostics, certain VPN or CNI-adjacent workloads) but should be treated with real scrutiny, since it enables packet crafting that's also useful for spoofing and reconnaissance if the container is compromised. Anything beyond this short, well-understood list is worth a specific security review before it goes in an add list — broad capabilities like SYS_ADMIN or SYS_PTRACE should be rare exceptions, not defaults copied from an old manifest.
How do you find out what capabilities a workload actually needs?
Run the workload with drop: ["ALL"] in a staging environment and observe what breaks — a permission-denied error in application logs pointing at a specific syscall class is the signal to add back exactly the capability that syscall requires, not a broader set "just in case." Tools that trace syscalls (strace, or eBPF-based observability tooling) can identify the specific capability a process needs more precisely than trial and error. This process is worth doing once per workload type and capturing in the pod spec permanently, rather than re-deriving it from scratch — and it's exactly the kind of configuration drift that policy-as-code admission controllers (OPA/Gatekeeper, Kyverno) can enforce going forward, rejecting any pod spec that doesn't explicitly drop ALL and justify each addition.
FAQ
Does dropping all capabilities break most applications?
No — the overwhelming majority of application containers (web servers behind a Service, API backends, batch jobs) function fine with zero Linux capabilities, since they don't perform privileged kernel operations directly. The workloads that need specific capabilities added back are a minority, typically things that bind low ports or manipulate raw sockets.
Is securityContext.capabilities the same as Kubernetes RBAC?
No. RBAC governs what a user or service account can do against the Kubernetes API (create pods, read secrets); capabilities govern what a running container process can do at the Linux kernel level. Both matter for a hardened cluster, but they control entirely different layers.
Can a Pod Security Standard enforce capability restrictions cluster-wide?
Yes — the "restricted" Pod Security Standard (the built-in replacement for the deprecated PodSecurityPolicy) requires drop: ["ALL"] and disallows most added capabilities, giving cluster operators a way to enforce this baseline without writing custom admission policy from scratch.
What happens if a needed capability is missing from a container's set?
The specific syscall requiring that capability fails, usually surfacing as a permission-denied error in the application's own logs rather than a Kubernetes-level event — which is why testing in staging with tracing enabled is more reliable than guessing.