Three cumulative levels
- Privileged — no restrictions. The equivalent of no policy at all.
- Baseline — blocks the known, obvious privilege-escalation paths (host namespaces, privileged containers, most dangerous capabilities) while still allowing a default, unspecified Pod to run without complaint.
- Restricted — current Pod hardening best practice. Everything Baseline blocks, plus requiring the specific hardening flags that page covers to actually be set, not just allowed.
What Restricted actually requires
Specifically, every container (and init and ephemeral container) in a Pod must set:
runAsNonRoot: true— at the Pod level, or on every container individually.runAsUser, if set at all, must not be0.allowPrivilegeEscalation: false.seccompProfile.typeset toRuntimeDefaultorLocalhost-- explicitly. NeitherUnconfinednor leaving it unset at all is allowed, which is a real difference from Docker's own default of just quietly applying its default profile for you.capabilities.dropmust includeALL, and the only capabilitycapabilities.addis allowed to add back isNET_BIND_SERVICE.- Volumes are limited to a safe set --
configMap,secret,emptyDir,persistentVolumeClaim,projected,downwardAPI,csi, andephemeral. Notably absent:hostPath.
containers:
- name: app
image: docktionary/app:1.4.0
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
seccompProfile:
type: RuntimeDefault
capabilities:
drop: ["ALL"]Applying it: namespace labels, not a Pod field
Pod Security Admission -- the thing that actually reads and enforces these levels -- is built into the API server and configured entirely through labels on the Namespace object itself, with no separate policy resource to create:
apiVersion: v1
kind: Namespace
metadata:
name: staging
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedThree independent modes, each with its own level: enforce rejects a violating Pod outright, audit allows it but adds an annotation to the audit log, and warn allows it but returns a warning to whoever ran kubectl apply. A namespace can run all three at different levels at once -- which is exactly how you roll this out without an outage.
Rolling it out without breaking what's already running
Setting enforce on a namespace does not retroactively touch Pods that are already running. Pod Security Admission checks happen at Pod creation (and on updates to most fields), not continuously -- an existing non-compliant Pod just keeps running undisturbed until something replaces it. That's exactly what makes a staged rollout possible:
# 1. warn + audit only -- see what would break, change nothing yet
kubectl label --overwrite ns staging \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
# 2. Fix whatever the warnings surfaced, then flip enforcement on.
# Existing, now-compliant Pods aren't affected either way -- this
# only changes what's accepted from here on.
kubectl label --overwrite ns staging \
pod-security.kubernetes.io/enforce=restrictedGoing straight to enforce on a namespace with Pods already running skips the one step that tells you what's about to start failing -- the next deploy, not the Pods sitting there right now.
Checking a manifest before it ever reaches a cluster
A real cluster with the labels set is the authoritative check, but it needs a cluster. For a quick local pass before that -- including the specific checks this page covers, like missing runAsNonRoot or a privileged container -- paste the manifest into the Kubernetes manifest linter. Against a real cluster, a dry run against the namespace is the exact check Pod Security Admission itself would do, with no Pod actually created:
kubectl apply --dry-run=server -n staging -f deployment.yamlWhat replaced PodSecurityPolicy
If you've seen PodSecurityPolicy (PSP) mentioned anywhere, it's the thing this replaced -- deprecated in Kubernetes 1.21 and removed entirely in 1.25. PSP was its own cluster-scoped resource with its own RBAC-based binding model, which was a large part of why it was hard to use correctly; Pod Security Admission trades that flexibility for the three fixed levels above, applied with a label instead of a resource and a binding.