Set security contexts
Add the per-pod and per-container settings that tell the kernel what a container may do: run as non-root, drop capabilities, block privilege escalation, and make the root filesystem read-only. A security context is a block in the pod or container spec that tells the Linux kernel what the container is allowed to do (it does not modify the container image). The platform sets none for you; you add them to your own workloads.
Coming from the workload side, this is the per-pod step in the workload-hardening ladder .
Warning
Platform components need elevated host access to function. Never apply restrictive Pod Security Standards to platform namespaces.
Pod-level and container-level settings#
You can place a securityContext block in two places in a pod spec:
Pod level (spec.securityContext): applies to all containers in the pod. Use this for settings that must be the same across all containers, such as the user and group IDs or the seccomp profile.
Container level (spec.containers[*].securityContext): applies to one container only and overrides the pod-level field for the same setting. Use this for per-container settings: privilege escalation, capabilities, and the read-only filesystem flag.
Some fields exist at only one level. allowPrivilegeEscalation, capabilities, and readOnlyRootFilesystem are container-only. seccompProfile works at either level; set it at the pod level to cover all containers.
Run as a non-root user#
Most images default to root (UID 0). Running as root gives a compromised container full control of the container filesystem.
runAsNonRoot: true tells Kubernetes to reject the container at startup if the image would run as root. Set it at the pod level:
spec:
securityContext:
runAsNonRoot: true
Use runAsUser and runAsGroup to set a specific numeric user ID and group ID. This is useful when the image does not declare its own user, or when a mounted volume requires a specific owner:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
Tip
If an image must run as root and you cannot change it, use a user namespace (hostUsers: false) instead. A user namespace maps container root to an unprivileged host user, so a container escape does not land as root on the node. See Run a workload in a user namespace .
Block privilege escalation#
Privilege escalation lets a process inside a container gain more permissions after it starts, for example by calling a setuid binary. Almost no application needs this.
Set allowPrivilegeEscalation: false at the container level:
securityContext:
allowPrivilegeEscalation: false
Linux capabilities#
Linux capabilities are fine-grained kernel privileges that control specific operations, such as binding a port below 1024, loading a kernel module, or changing file ownership. A container can run as non-root and still hold dangerous capabilities, or run as root with nearly all of them removed.
Drop all capabilities, then add back only what a specific container needs:
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # only if this container must bind a port below 1024
The containerd configuration lets containers bind low ports (below 1024) without extra privileges. So most containers do not need NET_BIND_SERVICE.
Tip
Start by dropping ALL with nothing in add. Run the container and check what fails. Add capabilities back one at a time when a specific error confirms they are needed.
Read-only root filesystem#
readOnlyRootFilesystem: true mounts the container's own filesystem as read-only. The container can still write to volumes you mount explicitly. It cannot write to directories inside the image itself.
securityContext:
readOnlyRootFilesystem: true
This stops a compromised container from modifying image files at runtime. It also makes all writable paths explicit in the pod spec, which is easier to audit.
Some images write to paths like /tmp on startup. Mount an emptyDir volume there:
volumes:
- name: tmp
emptyDir: {}
containers:
- name: app
volumeMounts:
- name: tmp
mountPath: /tmp
securityContext:
readOnlyRootFilesystem: true
Seccomp#
Seccomp (secure computing mode) is a Linux kernel feature that limits which system calls a container can make. A system call is a request from a process to the kernel, for example to open a file or create a network socket. Without a seccomp profile, a container can call any system call the kernel supports.
Every container runs under the RuntimeDefault seccomp profile by default. The kubelet sets seccompDefault: true, so a pod that names no profile gets the runtime's profile (it blocks around 44 dangerous syscalls) instead of running unconfined. Declaring it makes the node default explicit and portable:
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
Set this at the pod level so all containers in the pod get it. The restricted Pod Security Standard requires it. A workload that needs a blocked syscall opts out with seccompProfile.type: Unconfined, which baseline and restricted reject.
AppArmor#
AppArmor is mandatory access control: the kernel enforces a per-program policy that even a root process cannot bypass. Like the default seccomp profile, it is already on. Every non-privileged container runs under the runtime's enforcing profile without any field set.
The appArmorProfile field in the security context controls it, GA since Kubernetes 1.31. Its types: RuntimeDefault (the runtime's profile, same as leaving the field unset on this platform), Localhost (a custom profile loaded on the node), and Unconfined (no confinement).
spec:
securityContext:
appArmorProfile:
type: RuntimeDefault
See Use AppArmor for what the default profile blocks and how to load custom profiles.
A complete example#
This Deployment satisfies the restricted Pod Security Standard:
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
replicas: 1
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: your/image:tag
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
Common problems when applying this template:
- The image runs as root and has no non-root user configured. Add a user in the Dockerfile, or use a user namespace.
- The container writes to a directory inside the image at startup. Mount an
emptyDirvolume at that path. - The container needs a specific capability. Add it to
capabilities.addand document the reason.
Enforce these across a namespace#
Setting these fields on every pod by hand does not scale, and a single manifest that forgets them passes without notice. Pod Security Standards move the check to the namespace: label it once, and Kubernetes rejects any pod that does not meet the level. The restricted level requires exactly the context this page builds (non-root, dropped capabilities, seccomp RuntimeDefault). See Enforce Pod Security Standards for the levels and an audit-first rollout that will not break running workloads.
Add a user namespace#
A security context tells the kernel what a container may do. A user namespace changes who the container is on the host: set hostUsers: false and container root (UID 0) maps to an unprivileged host user, so an escape from a root container lands as "nobody" instead of host root. This is the answer when an image must run as root and you cannot change it. Pair it with the context above for the strongest per-pod isolation you can get without a VM. How user namespaces work is Workload runtimes and isolation ; the how-to is Run a workload in a user namespace .
What the platform sets#
Two things are on already: the runtime applies its enforcing AppArmor profile to every non-privileged container, and the kubelet defaults every container to the RuntimeDefault seccomp profile. Everything else here is your responsibility.
Every namespace runs at privileged until you label it. Platform components need privileges that the restricted level forbids. Applying a cluster-wide default that blocked privileged pods would break those platform components. You apply Pod Security Standards to your own namespaces, and you set security contexts on your own pods.
Related#
Workload runtimes and isolation
How the standard runtime, user namespaces, and the secure Kata runtime isolate workloads on Syself Autopilot, and which pod security controls you must configure.
Use AppArmor
Every non-privileged container on Syself Linux runs under an enforcing AppArmor profile by default. What the profile blocks, how to change it per pod, where SELinux stands, and how it fits the full workload hardening stack.