Security
11 min readMay 1, 2026Updated August 19, 2026

Kubernetes Pod Security Standards: Replacing PodSecurityPolicy

Part ofKubernetes
CO
Coding Protocols Team
Platform Engineering
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.

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

ProfileUse caseWhat it prevents
PrivilegedSystem-level components (CNI plugins, storage drivers, monitoring agents)Nothing — fully permissive
BaselineMost workloads; blocks known dangerous configurationshostNetwork, 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.
RestrictedSecurity-sensitive workloads; highest securityEverything 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):

bash
kubectl label namespace payments \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest

For 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:

yaml
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:

CapabilityNeeded for
NET_BIND_SERVICEBinding to ports < 1024 (HTTP on port 80/443)
NET_RAWRaw socket access (ping, some network tools)
CHOWNChanging 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:

bash
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:

yaml
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      - ALL

Kyverno 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:

yaml
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:

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

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

Was this article helpful?

Be the first to rate this article

Related Topics

Kubernetes
Pod Security
PSS
Security Context
Platform Engineering
EKS
Security
Kyverno

Found this useful? Share it.

Practice this

Related tools

Read Next