Linux Capabilities in Kubernetes

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Node runtime containerd://2.2.5; Linux kernel 6.12.0-211.34.1.el10_2.0.1.x86_64 (worker node)
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA · CKAD · CKS
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope Container-level capabilities.add and capabilities.drop, runtime capability inspection, drop ALL, safe CHOWN add-back, NET_BIND_SERVICE and runtime sysctl behaviour, Pod Security Admission restrictions, SYS_ADMIN implications, and privileged comparison. Does not cover full SecurityContext configuration, custom seccomp profiles, admission-policy setup, or Windows containers.
Related guides Authentication and admission control
Kubernetes RBAC
Kubernetes Pods and lifecycle

Traditional root privilege on Linux is split into individual capabilities. Kubernetes lets you grant or remove those privileges per container through securityContext.capabilities, without making the entire container privileged. This walkthrough inspects what containerd grants on this cluster, drops every default capability, then adds back only what a specific operation needs.

Create namespace cap-lab for the examples:

bash
kubectl create namespace cap-lab

Sample output:

output
namespace/cap-lab created

Understand Linux Capabilities

Container-Level Scope

On Linux, root is not a single on/off switch. Capabilities such as CHOWN and NET_BIND_SERVICE gate relatively focused operations, while SYS_ADMIN covers a broad collection of kernel administration functions.

In Kubernetes:

  • Capabilities are configured under the container-level securityContext.
  • They are Linux-specific; there is no Pod-level capabilities field.
  • Each container in the Pod, including sidecars and init containers, carries its own add and drop lists.

For runAsUser, fsGroup, readOnlyRootFilesystem, and other non-capability fields, see SecurityContext with practical examples.

Runtime Defaults Can Differ

Kubernetes does not ship one fixed starting capability list for every cluster.

The container runtime (containerd on this lab) applies its own defaults when the Pod spec omits capability rules. Those defaults can change when you upgrade the runtime, switch CRI implementations, or adjust node configuration.

Any capability mask in this article is observed output from containerd://2.2.5 on this cluster, not a guarantee for your environment. Always inspect the running container on your own nodes before you harden production manifests.

Understand the Capability Sets

When you read /proc/1/status or capsh --print, several capability sets appear:

  • Inheritable (CapInh) — capabilities that can participate in capability inheritance across execve().
  • Permitted (CapPrm) — the maximum set the process can place in its effective set.
  • Effective (CapEff) — capabilities the kernel checks when performing permission checks.
  • Bounding (CapBnd) — limits capabilities that can be gained when another executable is run.
  • Ambient (CapAmb) — capabilities preserved across execution of a non-privileged program when Linux inheritance rules permit it.

Inspect Current Container Capabilities

Read Capability Masks

Start with a diagnostic container and read the capability masks from /proc/1/status:

bash
kubectl run cap-busybox-default -n cap-lab --image=busybox:1.36 --restart=Never --command -- sh -c "grep ^Cap /proc/1/status; sleep 3600"

Sample output:

output
pod/cap-busybox-default created

Wait for the Pod to become Ready, then read the logs:

bash
kubectl wait --for=condition=Ready pod/cap-busybox-default -n cap-lab --timeout=60s

Sample output:

output
pod/cap-busybox-default condition met
bash
kubectl logs -n cap-lab cap-busybox-default

Sample output (observed on containerd://2.2.5):

output
CapInh:	0000000000000000
CapPrm:	00000000a80425fb
CapEff:	00000000a80425fb
CapBnd:	00000000a80425fb
CapAmb:	0000000000000000

Display Human-Readable Capability Names

When the image includes capsh, a human-readable summary is easier to read. The Fedora Minimal image below installs libcap at startup, then prints the Current line:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: cap-capsh-default
  namespace: cap-lab
spec:
  restartPolicy: Never
  containers:
  - name: app
    image: quay.io/fedora/fedora-minimal:44
    command:
    - sh
    - -c
    - |
      microdnf install -y libcap >/dev/null &&
      capsh --print | grep '^Current:'

This example requires outbound access to the Fedora package repositories while the container starts.

bash
kubectl apply -f cap-capsh-default.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cap-capsh-default -n cap-lab --timeout=120s

Sample output:

output
pod/cap-capsh-default created
pod/cap-capsh-default condition met
bash
kubectl logs -n cap-lab cap-capsh-default

Sample output (trimmed, observed on containerd://2.2.5):

output
Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=ep

The Current line shows the capability set observed by this process after Kubernetes and the container runtime started it.


Configure Capabilities in Kubernetes

Add and Drop YAML

Capability changes belong on a specific container:

yaml
securityContext:
  capabilities:
    drop:
    - ALL
    add:
    - NET_BIND_SERVICE

Keep these naming rules in mind:

  • Linux documentation uses CAP_NET_BIND_SERVICE.
  • Kubernetes YAML omits the CAP_ prefix — write NET_BIND_SERVICE.
  • add and drop apply to one container only; copy the block to every container that needs it.

Drop All Runtime Defaults

The primary least-privilege baseline is drop: ["ALL"], which removes every capability the runtime would otherwise grant.

yaml
apiVersion: v1
kind: Pod
metadata:
  name: cap-drop-all
  namespace: cap-lab
spec:
  restartPolicy: Never
  containers:
  - name: app
    image: busybox:1.36
    command: ["sh", "-c", "grep ^Cap /proc/1/status"]
    securityContext:
      capabilities:
        drop: ["ALL"]
bash
kubectl apply -f cap-drop-all.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cap-drop-all -n cap-lab --timeout=60s

Sample output:

output
pod/cap-drop-all created
pod/cap-drop-all condition met
bash
kubectl logs -n cap-lab cap-drop-all

Sample output:

output
CapInh:	0000000000000000
CapPrm:	0000000000000000
CapEff:	0000000000000000
CapBnd:	0000000000000000
CapAmb:	0000000000000000

With drop: ["ALL"], chown is denied. Apply a Pod that attempts to change ownership:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: cap-chown-fail
  namespace: cap-lab
spec:
  restartPolicy: Never
  containers:
  - name: app
    image: busybox:1.36
    command:
    - sh
    - -c
    - |
      touch /tmp/cap-test
      chown 1000 /tmp/cap-test 2>&1
    securityContext:
      capabilities:
        drop: ["ALL"]
bash
kubectl apply -f cap-chown-fail.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Failed pod/cap-chown-fail -n cap-lab --timeout=60s

Sample output:

output
pod/cap-chown-fail created
pod/cap-chown-fail condition met
bash
kubectl logs -n cap-lab cap-chown-fail

Sample output:

output
chown: /tmp/cap-test: Operation not permitted

Ordinary application behavior that does not need elevated kernel privileges continues to work.

Add Back CHOWN

Add CHOWN back when the workload must change file ownership:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: cap-chown-yes
  namespace: cap-lab
spec:
  restartPolicy: Never
  containers:
  - name: app
    image: busybox:1.36
    command:
    - sh
    - -c
    - |
      touch /tmp/cap-test
      chown 1000 /tmp/cap-test
      stat -c '%u' /tmp/cap-test
    securityContext:
      capabilities:
        drop: ["ALL"]
        add: ["CHOWN"]
bash
kubectl apply -f cap-chown-yes.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cap-chown-yes -n cap-lab --timeout=60s
kubectl logs -n cap-lab cap-chown-yes

Sample output:

output
pod/cap-chown-yes created
pod/cap-chown-yes condition met
1000

CHOWN restores only the permission needed to change file ownership. Do not test SYS_TIME by changing the clock on a Kubernetes node; inspect its capability mask instead.

NET_RAW permits raw and packet sockets, but ping is not a reliable capability test on current runtimes. Containerd can configure net.ipv4.ping_group_range so ordinary processes can send ICMP echo requests without NET_RAW. Test an actual raw-socket operation if your workload requires this capability.

The safe CHOWN before-and-after lab above proves the drop ALL and selective add-back workflow without relying on runtime networking defaults.

NET_BIND_SERVICE and Runtime Sysctls

NET_BIND_SERVICE allows binding to TCP/UDP ports below 1024. It appears in the observed default set on this cluster (cap_net_bind_service in the Current line above).

After drop: ["ALL"], add it back only when the workload truly needs a low port:

yaml
securityContext:
  runAsUser: 1000
  runAsNonRoot: true
  capabilities:
    drop:
    - ALL
    add:
    - NET_BIND_SERVICE

On current containerd CRI configurations, net.ipv4.ip_unprivileged_port_start is commonly set to 0 inside the Pod network namespace. In that case, a non-root process can bind to port 80 even when every capability mask is zero, so a successful low-port bind does not prove that NET_BIND_SERVICE is effective.

Inspect the actual value before testing:

bash
kubectl exec cap-busybox-default -n cap-lab -- cat /proc/sys/net/ipv4/ip_unprivileged_port_start

Observed output on the tested containerd://2.2.5 cluster:

output
0

Containerd 2.x enables this value by default for eligible CRI containers unless the runtime configuration overrides it.

A value of 0 allows unprivileged binding across the complete port range. Keep the NET_BIND_SERVICE YAML pattern for workloads and policy profiles that require it, but do not present an uncontrolled port-80 bind as a portable before-and-after capability test.

Pod Security Admission Restrictions

These examples focus on capability behavior and are not complete Restricted-profile manifests. A namespace enforcing Restricted also requires allowPrivilegeEscalation: false, runAsNonRoot: true, an approved seccomp profile, and drop: ["ALL"]; only NET_BIND_SERVICE may be added back. Run this lab in a namespace that does not enforce Restricted, or adapt every Pod to the complete Restricted requirements.


SYS_ADMIN and Privileged Containers

allowPrivilegeEscalation and Other Security Controls

SYS_ADMIN is a broad capability tied to many kernel administration paths. It is not a generic fix for file permission errors or volume mount problems.

Relationships worth remembering:

  • Prefer a narrower capability or a design change before you add SYS_ADMIN.
  • Kubernetes treats allowPrivilegeEscalation as always true when the container is privileged or has CAP_SYS_ADMIN.
  • Seccomp, AppArmor, SELinux, Pod Security Admission, and runAsUser all interact with the effective security posture. A capability grants a Linux privilege; it does not bypass every isolation layer.

This article does not include exploit or host-escape demonstrations. Treat any workload that requests SYS_ADMIN as a security review item.

Capabilities Compared with Privileged Mode

Capability configuration Privileged container
Grants selected privileges Receives all Linux capabilities
Normal seccomp and other controls can still apply Overrides several isolation controls
Narrower blast radius Broad host-level access
Preferred when one capability is sufficient Reserved for exceptional workloads

privileged: true is not equivalent to adding one capability. The SecurityContext guide covers privileged, runAsNonRoot, and read-only root filesystem settings separately.


Verify and Troubleshoot Capability Changes

Repeatable Test Sequence

Use this repeatable sequence on your cluster:

  1. Run a container with no capability rules and record the observed Current line or CapEff mask.
  2. Apply drop: ["ALL"] and confirm the effective set changed.
  3. Trigger an operation that needs a removed capability and confirm it fails.
  4. Add one capability with add.
  5. Re-run the same operation and confirm it succeeds.
  6. Confirm unrelated capabilities remain absent.

On this cluster, dropping all capabilities changed CapEff from 00000000a80425fb to all zeros, chown failed, and adding CHOWN restored only file-ownership changes.

Common Capability Mistakes

Mistake What goes wrong
Writing CAP_NET_BIND_SERVICE in YAML API expects NET_BIND_SERVICE without the CAP_ prefix
Placing capabilities under Pod-level spec.securityContext capabilities is not a valid PodSecurityContext field; normal strict kubectl validation rejects it. Configure securityContext.capabilities separately on each container
Assuming root means every capability Effective caps still follow runtime defaults unless privileged
Publishing one universal default list Runtime and version differences make static lists inaccurate
Using SYS_ADMIN as a broad workaround Grants far more than most apps need
Adding a capability while seccomp blocks the syscall Operation still fails; check multiple layers
Hardening only the main container Sidecars and init containers keep runtime defaults
Testing SYS_TIME with date -s on a cluster node Can change the worker node clock and disrupt kubelet, certificates, and logs

What's Next


References


Summary

Linux capabilities let you grant narrow kernel privileges without handing a container full root access. Kubernetes configures them per container through securityContext.capabilities.add and drop, not at the Pod level.

You inspected the runtime-provided set on a containerd://2.2.5 cluster, dropped every default with drop: ["ALL"], and confirmed that chown failed until you added back CHOWN. The NET_BIND_SERVICE YAML pattern is the same; verify low-port binding on your nodes because sysctl settings can change the outcome.

SYS_ADMIN and privileged: true sit at the opposite end of the spectrum from drop: ["ALL"] plus a single add. For runAsNonRoot, read-only root filesystem, and seccomp profiles, continue with the SecurityContext walkthrough.


Frequently Asked Questions

1. What is the difference between CAP_NET_BIND_SERVICE and NET_BIND_SERVICE in YAML?

Linux names capabilities with a CAP_ prefix. Kubernetes SecurityContext YAML omits that prefix, so you write NET_BIND_SERVICE in capabilities.add, not CAP_NET_BIND_SERVICE.

2. What does drop ALL do in Kubernetes SecurityContext?

It removes every capability from the container effective set that the runtime would otherwise grant. You then add back only the individual capabilities the workload needs.

3. Can I configure capabilities at the Pod securityContext level?

No. capabilities.add and capabilities.drop belong on each container securityContext. Every application container, sidecar, and init container needs its own capability list.

4. Is privileged mode the same as adding one Linux capability?

No. privileged grants broad host-level access and runs seccomp unconfined. Adding a single capability such as CHOWN or NET_BIND_SERVICE is much narrower.

5. Does Kubernetes define one universal default capability set?

No. The container runtime supplies the starting set. Defaults can differ between containerd, CRI-O, runtime versions, and node configuration, so treat lab output as observed behavior.

6. Why does my container still fail after I added a capability?

Seccomp, AppArmor, SELinux, Pod Security Admission, the container's UID/GID, or filesystem permissions can still block the operation. A capability grants a specific Linux privilege; it does not bypass every isolation or access-control layer.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)