The Kubernetes security context is where you decide how much power a container gets, and getting a handful of fields right — runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation: false, dropped capabilities, and a seccomp profile — is the difference between a compromised container that stays contained and one that becomes a launchpad to the node. Most workloads ship with an empty securityContext, which means Kubernetes defaults, which means far more privilege than the app needs. This post covers the settings that matter and gives you a baseline block to paste.
What is a Kubernetes SecurityContext?
A securityContext is a block of security settings you attach either to a whole pod (spec.securityContext) or to an individual container (spec.containers[].securityContext). It controls identity (which user and group the process runs as), privilege (Linux capabilities, privilege escalation, privileged mode), and isolation (the filesystem, seccomp, and SELinux/AppArmor profiles applied).
The container-level context overrides the pod-level one where they overlap, so a common pattern is to set safe defaults at the pod level and tighten specific containers as needed. The reason to care: by default, Kubernetes runs containers with the image's configured user (often root), a writable root filesystem, and a default capability set. None of that is least privilege — it is convenience, and convenience is what an attacker inherits after a container compromise.
Which SecurityContext settings reduce blast radius most?
A few fields do most of the work.
runAsNonRoot: true and runAsUser. Run the process as an unprivileged user. If a container runs as root and an attacker gains code execution, they are root inside the container — one kernel bug or misconfiguration away from the node. Setting runAsNonRoot: true makes Kubernetes refuse to start a container that would run as UID 0, catching images that quietly default to root.
allowPrivilegeEscalation: false. Blocks a process from gaining more privileges than its parent via setuid binaries or file capabilities. This is one of the highest-value single settings and should be the default for essentially every workload.
readOnlyRootFilesystem: true. Makes the container's root filesystem immutable. An attacker cannot write a payload, modify binaries, or drop a web shell onto disk. Where the app needs scratch space, mount a specific writable emptyDir volume instead of leaving the whole filesystem writable.
Drop capabilities. Remove all Linux capabilities and add back only what the workload genuinely needs:
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # only if binding to a port below 1024
Most application containers need no capabilities at all. NET_BIND_SERVICE is the common exception, and even that is avoidable by binding to a higher port.
seccompProfile. Set type: RuntimeDefault to apply the runtime's default seccomp filter, which blocks dozens of dangerous syscalls a normal app never uses. This is off unless you set it, and turning it on is nearly free.
Never privileged: true. Privileged mode disables the isolation this whole context exists to provide. Treat any privileged: true in a manifest as a finding to justify or remove.
What does a hardened baseline look like?
Here is a securitycontext kubernetes block that combines the settings above into a sensible default for a typical stateless service:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Every workload will not accept this unchanged — some need a writable path, a specific capability, or a particular UID — but starting from locked-down and loosening deliberately beats starting from open and hoping to remember to tighten. The writable /tmp mount is the usual concession that lets readOnlyRootFilesystem: true work with apps that expect scratch space.
How does this relate to Pod Security Standards?
Kubernetes retired Pod Security Policies and replaced them with Pod Security Admission, which enforces three Pod Security Standards levels — privileged, baseline, and restricted — at the namespace level. The restricted profile is essentially a policy wrapper around the securityContext fields above: it requires runAsNonRoot, allowPrivilegeEscalation: false, dropped capabilities, and a seccomp profile.
So there are two complementary layers. The securityContext on each workload sets the actual controls; Pod Security Admission (or a policy engine like Kyverno or OPA Gatekeeper) enforces that workloads cannot ship without them. Label a namespace to enforce restricted and Kubernetes will reject pods that omit these fields — turning best practice into a gate rather than a hope. Wiring that check into CI, so manifests are validated before they ever reach the cluster, is the same shift-left move that dependency and image scanning follow; we cover it across the blog, and our container and SCA scanning feeds the image side of the same picture.
How do you audit existing workloads?
Auditing beats rewriting everything at once. Inspect running pods for the risky defaults: containers running as root, privileged: true, allowPrivilegeEscalation unset, writable root filesystems, and full capability sets. Open-source tools help — kubescape and kube-bench check workloads and clusters against hardening benchmarks, and a policy engine can report violations before you enforce them. Roll out enforcement gradually: run Pod Security Admission in warn or audit mode first to see what would break, fix the workloads, then flip to enforce. That sequence avoids the outage that comes from enforcing restricted on a cluster full of root-running pods.
FAQ
What is the single most important securityContext setting?
allowPrivilegeEscalation: false combined with runAsNonRoot: true gives the most protection for the least effort. Together they stop a compromised process from being root and from escalating to root, which removes the most common path from container compromise to node compromise. Add dropped capabilities and a read-only root filesystem next.
Does readOnlyRootFilesystem break applications?
It can, if the app writes to its own filesystem — logs, caches, temp files. The fix is not to disable it but to mount specific writable volumes (usually an emptyDir at /tmp or a cache path) while keeping the rest immutable. Most apps work fine with one or two mounted writable directories.
What is the difference between pod-level and container-level securityContext?
Pod-level settings (spec.securityContext) apply to all containers in the pod and cover pod-wide options like fsGroup. Container-level settings (spec.containers[].securityContext) apply to one container and override pod-level values where they conflict. Fields like capabilities and readOnlyRootFilesystem are container-level; fsGroup is pod-level.
How do I enforce these settings across a cluster?
Use Pod Security Admission with the restricted profile at the namespace level, or a policy engine like Kyverno or OPA Gatekeeper for finer control. Start in warn/audit mode to find non-compliant workloads without breaking them, remediate, then switch to enforce so no future pod can ship without the required securityContext fields.