Security contexts

runAsNonRoot, drop capabilities, no privilege escalation.

Advanced12 min · lesson 13 of 24

A securityContext is where you tell Kubernetes what a container is and is not allowed to be at the OS level — the user it runs as, whether it can gain new privileges, which Linux capabilities it holds, and whether its root filesystem is writable. It is the single most important block on a pod spec from a security standpoint, because it decides how much a compromised process can do before it even reaches the network or the API.

The core is always the same three settings: run as a non-root numeric UID, forbid privilege escalation, and drop every capability, adding back only the few a proven need requires. Root inside a container is, absent user-namespace remapping, the same UID 0 the host kernel knows — one bad mount or kernel bug from host root — so non-root is the highest-value line in the whole spec.

pod.yaml
spec:
securityContext: # pod-level: applies to all containers
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile: { type: RuntimeDefault }
containers:
- name: app
image: registry.internal/app:1.4.2
securityContext: # container-level: overrides / adds
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # only if it truly binds a port < 1024

Pod scope vs container scope

Some fields live at pod level and apply to every container — runAsUser, runAsNonRoot, fsGroup, seccompProfile — while others are per-container: capabilities, allowPrivilegeEscalation, readOnlyRootFilesystem, privileged. A container-level value overrides the pod default, which is how you set a strict baseline for the whole pod and grant one container a single, visible exception rather than loosening everything.

Capabilities in practice

Linux splits root’s power into ~40 capabilities, and a container gets a default subset it almost never fully uses. Drop ALL, then add back only what the app demonstrably needs — NET_BIND_SERVICE to bind a low port, and rarely anything else. The frequent mistake is adding NET_ADMIN or SYS_ADMIN “to be safe”; SYS_ADMIN in particular is so broad it is nicknamed the new root.

terminal
# what the process actually holds at runtime
$ kubectl exec app -- grep Cap /proc/1/status
CapEff: 0000000000000400 # only NET_BIND_SERVICE remains
$ kubectl exec app -- id
uid=10001 gid=10001 groups=10001 # non-root, as declared
The red flags
privileged: true, hostPID, hostIPC, hostNetwork, hostPath volume mounts, and running as UID 0 are the settings that turn a container escape into a host takeover — privileged alone disables seccomp, capability drops, and device restrictions at once. If a manifest asks for any of them, that is the first thing to challenge and almost never the thing to grant.