Kubernetes Pod Security Standards: Replacing PodSecurityPolicy

Quick answer
The three Pod Security Standards profiles — Privileged, Baseline, and Restricted — define what's allowed in a pod spec. This is the practical side: the exact securityContext that satisfies Restricted, which capabilities are safe to add back, Helm chart compatibility gaps, and the Kyverno mutation pattern that enforces it without editing every chart.
- The Three Profiles
- Enforcing PSS via Namespace Labels
- Security Context: Satisfying the Restricted Profile
- Cluster-Wide Defaults
- Finding Restricted Violations on EKS Before Enforcing
11 min read · Security
PodSecurityPolicy (PSP) was deprecated in Kubernetes 1.21 and removed in 1.25. Pod Security Standards (PSS) replaced it with three predefined profiles enforced by a built-in admission controller. The profiles are intentionally opinionated — they cover the security posture of most workloads without requiring custom policy authoring.
For policies beyond what PSS profiles express (requiring specific images, enforcing labels, blocking specific capabilities), pair PSS with Kyverno or OPA Gatekeeper.
The Three Profiles
| Profile | Use case | What it prevents |
|---|---|---|
| Privileged | System-level components (CNI plugins, storage drivers, monitoring agents) | Nothing — fully permissive |
| Baseline | Most workloads; blocks known dangerous configurations | hostNetwork, hostPID, hostIPC, hostPorts, privileged containers, NET_RAW and other non-default capabilities, unsafe sysctls, custom /proc mounts. hostPath volumes and allowPrivilegeEscalation are NOT restricted by Baseline — that is a Restricted-only requirement. Use the Restricted profile or Kyverno/OPA to enforce them. |
| Restricted | Security-sensitive workloads; highest security | Everything in Baseline + requires runAsNonRoot: true, requires allowPrivilegeEscalation: false, drops all capabilities, requires seccompProfile.type: RuntimeDefault or Localhost, restricts volumes to configMap, secret, projected, downwardAPI, emptyDir, csi, ephemeral, persistentVolumeClaim |
The Restricted profile is the target for production workloads that don't need node-level access.
Enforcing PSS via Namespace Labels
PSS is enforced by labeling namespaces with the profile you want, in one of three modes (enforce rejects violations, audit logs them, warn returns a client warning):
kubectl label namespace payments \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latestFor the full mechanics of the admission controller behind this — enforcement modes, cluster-wide defaults via AdmissionConfiguration, and the step-by-step migration off PodSecurityPolicy — see Kubernetes Pod Security Admission: The PodSecurityPolicy Replacement Guide. The rest of this post covers what to actually put in your pod specs to satisfy each profile.
Security Context: Satisfying the Restricted Profile
A pod that satisfies the Restricted profile:
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: payments-api
5 namespace: payments
6spec:
7 template:
8 spec:
9 # Pod-level security context
10 securityContext:
11 runAsNonRoot: true # Required by Restricted
12 runAsUser: 1000 # UID for the process; must be non-zero
13 runAsGroup: 1000
14 fsGroup: 1000 # Files in mounted volumes owned by this GID
15 seccompProfile:
16 type: RuntimeDefault # Required by Restricted — RuntimeDefault or Localhost are both accepted
17
18 containers:
19 - name: payments-api
20 image: payments-api:1.2.0
21 # Container-level security context
22 securityContext:
23 allowPrivilegeEscalation: false # Required by Restricted
24 readOnlyRootFilesystem: true # Best practice; NOT required by PSS Restricted — enforce via Kyverno if needed
25 capabilities:
26 drop:
27 - ALL # Required by Restricted — drop all Linux capabilities
28 add: [] # Add back only what you need (NET_BIND_SERVICE for port < 1024)
29
30 # If the container needs to write files, use emptyDir volumes
31 volumeMounts:
32 - name: tmp
33 mountPath: /tmp
34 - name: cache
35 mountPath: /app/cache
36
37 volumes:
38 - name: tmp
39 emptyDir: {}
40 - name: cache
41 emptyDir: {}Common capabilities to add back when needed:
| Capability | Needed for |
|---|---|
NET_BIND_SERVICE | Binding to ports < 1024 (HTTP on port 80/443) |
NET_RAW | Raw socket access (ping, some network tools) |
CHOWN | Changing file ownership (avoid — run as correct UID instead) |
For most web services: drop ALL, add nothing. Run the application on port 8080 instead of 80.
Cluster-Wide Defaults
Rather than labeling every namespace individually, a cluster-wide AdmissionConfiguration can set a default profile for any namespace that doesn't have explicit labels — see the PSA guide for the full config and exemption syntax. On EKS specifically, you can't pass custom API server flags, so use namespace labels directly and lean on Kyverno or OPA Gatekeeper for organization-wide baselines instead.
Server & SSH Hardening Checklist
The firewall, SSH, fail2ban, and update baseline every internet-facing Linux box should pass. Plain Markdown you can run down in an afternoon.
Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.
Finding Restricted Violations on EKS Before Enforcing
audit and warn mode surface violations without rejecting anything — see the PSA guide for the full warn-mode migration workflow. On EKS, the violations land in CloudWatch Logs rather than a local audit log file:
aws logs filter-log-events \
--log-group-name "/aws/eks/production/cluster" \
--filter-pattern '{ $.annotations["pod-security.kubernetes.io/audit-violations"] = "*" }'Most violations trace back to the same four things: running as root, a missing seccompProfile, allowPrivilegeEscalation left unset, or capabilities not dropped — the securityContext fields covered above.
Helm Chart Compatibility
Many Helm charts ship with security contexts that don't satisfy Restricted. The kube-prometheus-stack, for example, uses hostNetwork: true for some components and requires the monitoring namespace to be Privileged or Baseline. Check the chart's documentation for the minimum PSS profile it supports.
For charts you control, add securityContext values to the values.yaml defaults:
1# values.yaml defaults that satisfy Restricted
2podSecurityContext:
3 runAsNonRoot: true
4 runAsUser: 1000
5 fsGroup: 1000
6 seccompProfile:
7 type: RuntimeDefault
8
9securityContext:
10 allowPrivilegeEscalation: false
11 readOnlyRootFilesystem: true
12 capabilities:
13 drop:
14 - ALLKyverno for Policies Beyond PSS Profiles
PSS profiles cover privilege escalation. For policies that PSS doesn't express — requiring specific image registries, enforcing resource limits on all pods, blocking latest tags — use Kyverno:
1# Block pods that don't set CPU/memory limits (PSS doesn't require this)
2apiVersion: kyverno.io/v1
3kind: ClusterPolicy
4metadata:
5 name: require-resource-limits
6spec:
7 validationFailureAction: Enforce
8 rules:
9 - name: check-cpu-memory-limits
10 match:
11 any:
12 - resources:
13 kinds: [Pod]
14 exclude:
15 any:
16 - resources:
17 kinds: [Pod]
18 namespaces: ["kube-system", "kube-public", "kube-node-lease"]
19 validate:
20 message: "CPU and memory limits are required on all containers"
21 pattern:
22 spec:
23 containers:
24 - resources:
25 limits:
26 cpu: "?*"
27 memory: "?*"PSS handles the security posture (privilege escalation, host access); Kyverno handles operational policies (resource limits, image registries, required labels). They complement each other.
Secure by Default: Kyverno for PSS Mutation
In 2026, rather than blocking non-compliant pods, platform teams use Kyverno to mutate them into compliance. This "secure by default" pattern automatically injects the required securityContext fields, allowing developers to focus on code rather than security YAML:
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: mutate-pss-restricted
5spec:
6 rules:
7 - name: set-restricted-context
8 match:
9 any:
10 - resources:
11 kinds: [Pod]
12 namespaces: ["payments", "orders"]
13 mutate:
14 patchStrategicMerge:
15 spec:
16 securityContext:
17 runAsNonRoot: true
18 seccompProfile:
19 type: RuntimeDefault
20 containers:
21 - name: "*"
22 securityContext:
23 allowPrivilegeEscalation: false
24 capabilities:
25 drop: [ALL]By mutating pods to meet the Restricted profile, you enforce a high security baseline across the cluster without requiring every team to update their Helm charts manually.
See also
- Enforcing Policy with Kyverno: Validate, Mutate, Generate
- Pod Security Admission: Replace PodSecurityPolicy the Right Way
Frequently Asked Questions
What's the difference between seccompProfile.type: RuntimeDefault and Localhost?
RuntimeDefault uses the container runtime's built-in seccomp profile (the one Docker/containerd ships with). It blocks ~300 system calls not needed by typical applications. Localhost points to a custom seccomp profile file on the node. RuntimeDefault is the right starting point for most workloads — it satisfies the Restricted profile requirement and reduces syscall attack surface without requiring custom profiles.
Does PSS enforce security contexts on init containers?
Yes. The Pod Security admission controller checks init containers, ephemeral containers, and regular containers — all must satisfy the profile's requirements. A pod with a compliant main container but a non-compliant init container will be rejected.
How do I handle a third-party container that runs as root?
Three options: (1) Add runAsUser to override the UID at the pod/container level — works if the container doesn't require root privileges for its actual function, just was built with root as default. (2) Use the Baseline profile for that namespace instead of Restricted. (3) Run the container in a dedicated namespace with a more permissive profile while enforcing Restricted everywhere else. Option 1 is best when feasible — check that the application actually works as non-root.
For admission webhook policies that complement PSS (enforcing image registries, blocking privileged namespaces), see Kubernetes Admission Webhooks: OPA Gatekeeper and Kyverno. For RBAC policies that limit who can create privileged pods, see Kubernetes RBAC Advanced Patterns. For supply chain security and image signing that pairs with PSS enforcement, see Container Image Security: Supply Chain from Build to Production.
For the two objects these profiles actually constrain, see PodSecurityContext vs SecurityContext.
Migrating from PodSecurityPolicy to PSS or enforcing the Restricted profile across a multi-team cluster? Talk to us at Coding Protocols — we help platform teams implement security baselines that don't break running workloads.
Official References
- Pod Security Standards — the Privileged, Baseline and Restricted profiles
- Configure a security context — runAsUser, capabilities and seccomp fields
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


