Kubernetes Security Contexts and Pod Security Standards
A Pod is a wrapper around one or more containers. By default, a container in Kubernetes inherits very permissive runtime settings — it can run as root, write to the host filesystem, and even escape to the underlying node under certain conditions. Security Contexts and Pod Security Standards are the two mechanisms Kubernetes provides to tighten these defaults and enforce safe, predictable behaviour.
What Is a Security Context?
A Security Context is a set of Linux-level settings applied to a Pod or a specific container at runtime. These settings control things like which user the process runs as, whether the root filesystem is read-only, and which Linux capabilities the process holds.
Think of it like setting the rules before handing a guest the keys to your house: "You can use the living room and kitchen, but you cannot enter the server room or change the locks."
Where Security Contexts Live
┌────────────────────────────────────────────────┐ │ Pod Spec │ │ │ │ spec: │ │ securityContext: ← Pod-level │ │ runAsUser: 1000 │ │ fsGroup: 2000 │ │ │ │ containers: │ │ - name: app │ │ securityContext: ← Container-level │ │ allowPrivilegeEscalation: false │ │ readOnlyRootFilesystem: true │ └────────────────────────────────────────────────┘
Pod-level settings apply to all containers in the Pod. Container-level settings apply only to that specific container and override pod-level settings when both are set for the same field.
Pod-Level Security Context Fields
runAsUser and runAsGroup
These set the UID and GID the container process runs as. If your container image expects to run as root (UID 0), and you set runAsUser: 1000, the process starts as a non-root user instead.
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
Running as a non-root user is one of the single most effective security improvements you can make. A vulnerability that gives an attacker code execution inside the container gives them far less power when the process runs as UID 1000 compared to UID 0.
fsGroup
When a Pod mounts a volume, fsGroup sets the group ownership of files on that volume. All processes in the Pod inherit this group, making it possible for multiple containers in a Pod to share mounted files with correct permissions.
spec:
securityContext:
fsGroup: 2000
runAsNonRoot
Setting runAsNonRoot: true makes Kubernetes refuse to start the container if the image would run as root. This acts as a safety check — even if someone forgets to set runAsUser, the Pod fails to start rather than silently running as root.
spec:
securityContext:
runAsNonRoot: true
seccompProfile
Seccomp (Secure Computing Mode) restricts which Linux system calls a container can make. The RuntimeDefault profile applies the container runtime's recommended syscall filter, blocking a large set of dangerous calls with no application configuration needed.
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
Container-Level Security Context Fields
allowPrivilegeEscalation
This setting controls whether a process can gain more privileges than its parent. Always set this to false. Without it, a process inside the container could use setuid binaries or other techniques to escalate to root even if the container started as a non-root user.
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem
Setting this to true mounts the container's root filesystem as read-only. The application cannot write to /etc, /usr, /bin, or any other system directory. If an attacker gains code execution, they cannot drop new binaries or modify system files.
Applications that need to write files should use explicitly mounted volumes (emptyDir or PVCs) rather than the root filesystem.
containers:
- name: app
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: /tmp
name: tmp-dir
volumes:
- name: tmp-dir
emptyDir: {}
capabilities: drop and add
Linux capabilities divide the traditional all-or-nothing root privileges into smaller, individually grantable permissions. A container starts with a default set of capabilities. You should drop all of them and add back only what the application genuinely needs.
containers:
- name: app
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # Only if app binds to port < 1024
Dropping ALL capabilities removes dangerous abilities like loading kernel modules, changing network settings, and bypassing file permission checks. Most web applications, APIs, and workers need zero capabilities when running as a non-root user on ports above 1024.
privileged
A privileged container has almost all the same capabilities as the host root user. It can see and modify host devices, load kernel modules, and break out of container isolation. Never set privileged: true in production unless you are running a very specific low-level system tool.
A Hardened Container: Full Example
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- mountPath: /tmp
name: tmp-dir
- mountPath: /app/cache
name: cache-dir
volumes:
- name: tmp-dir
emptyDir: {}
- name: cache-dir
emptyDir: {}
This Pod runs as UID 1000, cannot escalate privileges, has a read-only root filesystem, holds zero Linux capabilities, and uses the default seccomp profile. This is a solid baseline for any production workload.
Pod Security Standards: Cluster-Wide Enforcement
Security Contexts are powerful, but they rely on developers setting them correctly per Pod. Pod Security Standards (PSS) are a higher-level mechanism that lets cluster operators enforce a minimum security baseline across an entire namespace — automatically, without trusting developers to remember.
PSS replaced the older Pod Security Policy (PSP) feature, which was removed in Kubernetes 1.25.
The Three Security Profiles
┌─────────────────────────────────────────────────────────────┐ │ Pod Security Standard Profiles │ │ │ │ PRIVILEGED BASELINE RESTRICTED │ │ ─────────── ──────── ────────── │ │ No restrictions Blocks known Heavily locked │ │ escalation vectors down; enforces │ │ Use for: Use for: best practices │ │ System-level General workloads Use for: │ │ tools (CNI, that don't need Internet-facing │ │ storage drivers) root or host access apps, user data │ └─────────────────────────────────────────────────────────────┘
Privileged Profile
No restrictions. Any Pod configuration is accepted. Use this only for infrastructure namespaces that run DaemonSets for networking, storage, or logging — components that genuinely need host-level access.
Baseline Profile
Blocks the most dangerous settings: privileged containers, host network/PID/IPC namespaces, dangerous host volume types, and unsafe capabilities. Most workloads pass this profile without any changes.
Restricted Profile
Enforces the full set of security best practices. Pods must run as non-root, must drop all capabilities, must use a seccomp profile, and cannot use any host namespaces or volumes. This is the profile to target for any public-facing application.
Applying Pod Security Standards to a Namespace
PSS works via labels on the namespace. You set the profile and the mode (enforce, audit, or warn).
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
The Three Modes
- enforce — Pods that violate the policy are rejected. They will not start.
- audit — Violations are recorded in the audit log but the Pod still starts. Use this to discover violations before enforcing.
- warn — A warning message is returned to the user when they apply a violating manifest. The Pod still starts. Useful as a gentle reminder during migration.
A recommended rollout strategy: start with warn and audit for a few weeks. Fix the workloads that trigger warnings. Then switch to enforce when the namespace is clean.
PSS Migration: From No Policy to Restricted
Stage 1: Discover Label namespace with audit + warn at "restricted" Watch audit logs and kubectl warnings for 1–2 weeks Stage 2: Fix Update Deployments/StatefulSets to pass restricted checks Add securityContext fields, switch to non-root users Stage 3: Enforce Add enforce label at "restricted" Non-compliant Pods are now blocked at admission
Host Namespaces: What to Block and Why
Kubernetes Pods can optionally share namespaces with the host node. Each of these is a serious security risk:
- hostNetwork: true — The Pod sees and can bind to the host's network interfaces. A compromised Pod can sniff all traffic on the node.
- hostPID: true — The Pod can see all processes running on the host, including other Pods and system processes. It can send signals to them.
- hostIPC: true — The Pod shares the host's inter-process communication namespace and can communicate with host processes.
The Baseline and Restricted PSS profiles block all three. Only the Privileged profile allows them, and they should appear only in DaemonSets for approved system tools.
Volume Security: hostPath Is Dangerous
A hostPath volume mounts a directory from the host node directly into the Pod. If the application writes to /etc or the container runs as root, it can modify host configuration files. The Baseline profile blocks the most dangerous hostPath targets; the Restricted profile blocks all hostPath volumes.
Use emptyDir, PersistentVolumeClaims, or ConfigMap/Secret mounts instead of hostPath for application workloads.
Checking Compliance Before Enforcing
# Dry-run: what would happen if you enforced "restricted" on a namespace? kubectl label namespace production \ pod-security.kubernetes.io/enforce=restricted \ --dry-run=server # Check an existing namespace for violations kubectl --dry-run=server apply -f my-deployment.yaml \ --namespace production
Key Points
- A Security Context applies Linux runtime settings — user, capabilities, filesystem access — to a Pod or container.
- Run containers as a non-root user, set
allowPrivilegeEscalation: false, enablereadOnlyRootFilesystem, and drop all capabilities for every production workload. - Pod Security Standards enforce a minimum security profile at the namespace level via labels.
- The three PSS profiles are Privileged (no restrictions), Baseline (blocks known exploits), and Restricted (enforces best practices).
- Use
warnandauditmodes to discover violations before switching toenforce. - Avoid
hostNetwork,hostPID,hostIPC, andhostPathin application workloads.
