Security contexts
runAsNonRoot, drop capabilities, no privilege escalation.
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.
spec:securityContext: # pod-level: applies to all containersrunAsNonRoot: truerunAsUser: 10001fsGroup: 10001seccompProfile: { type: RuntimeDefault }containers:- name: appimage: registry.internal/app:1.4.2securityContext: # container-level: overrides / addsallowPrivilegeEscalation: falsereadOnlyRootFilesystem: truecapabilities: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.
# what the process actually holds at runtime$ kubectl exec app -- grep Cap /proc/1/statusCapEff: 0000000000000400 # only NET_BIND_SERVICE remains$ kubectl exec app -- iduid=10001 gid=10001 groups=10001 # non-root, as declared