Pod Security Basics
This page introduces the Pod Security Standards (privileged, baseline, restricted) and how Pod Security Admission enforces them per namespace. The examples move from labeling a namespace to shipping a pod that satisfies the strictest profile.
Prerequisites
- Kubernetes 1.36.2 cluster (any conformant distribution). Pod Security Admission is built in and needs no install.
kubectlmatching your cluster minor version.- Cluster-admin or namespace-admin rights to set namespace labels.
- Optional: a policy engine such as Kyverno or OPA Gatekeeper for rules beyond the three standard profiles.
Check your access:
kubectl auth can-i label namespaces
kubectl version --shortBasic Examples
1. The three Pod Security Standards
Pod Security Standards define three profiles that get progressively stricter.
- Privileged: unrestricted. Intended for infrastructure and trusted system workloads only.
- Baseline: blocks known privilege escalations such as host namespaces, privileged containers, and hostPath volumes.
- Restricted: everything in baseline plus
runAsNonRoot, a seccomp profile, dropped capabilities, and no privilege escalation. - Aim for restricted in application namespaces and reserve privileged for system components.
2. Enforce a profile on a namespace
Pod Security Admission reads labels on the namespace to decide what to allow.
apiVersion: v1
kind: Namespace
metadata:
name: web
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest- The
enforcelabel rejects pods that violate the named profile. enforce-version: latesttracks the profile as it evolves across releases.- Set it to a pinned version like
v1.36if you want stable behavior across upgrades. - PSA acts at admission time, so violating pods never get scheduled.
3. Warn and audit before you enforce
You can observe violations without blocking any deploys.
kubectl label namespace web \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restrictedwarnreturns a message to the client (visible inkubectland CI logs).auditwrites a violation entry to the API server audit log.- This is the safe way to dry-run a stricter profile before enforcing it.
- Read the warnings, fix the workloads, then raise
enforce.
4. A pod that fails restricted
This spec runs as root and keeps all capabilities, so restricted rejects it.
apiVersion: v1
kind: Pod
metadata:
name: bad
spec:
containers:
- name: app
image: nginx:1.27- No
securityContextmeans the container may run as UID 0. restrictedrequiresrunAsNonRoot: true, so admission denies this pod.- The error names the exact fields that violate the profile.
- Treat the rejection message as a checklist for what to add.
5. A minimal restricted-compliant pod
Add the fields restricted requires and the same pod is admitted.
apiVersion: v1
kind: Pod
metadata:
name: good
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginxinc/nginx-unprivileged:1.27
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]runAsNonRootandseccompProfile: RuntimeDefaultsit at the pod level.allowPrivilegeEscalation: falseanddrop: ["ALL"]sit on the container.- The unprivileged image listens on a high port so it does not need root.
- These four settings cover the core of the restricted profile.
6. Verify what a namespace enforces
Inspect the labels to confirm the active posture.
kubectl get namespace web -o jsonpath='{.metadata.labels}' | tr ',' '\n'- The output lists every
pod-security.kubernetes.io/*label in effect. - Missing labels mean the namespace inherits the cluster default (usually privileged).
- Make posture explicit rather than relying on defaults.
- Include these labels in the manifests you keep under version control.
7. Test admission with a dry run
Server-side dry run tells you if a pod would be admitted, without creating it.
kubectl apply -f bad-pod.yaml --dry-run=server--dry-run=serverruns admission, including PSA, and reports violations.- Use it in CI to catch policy failures before merge.
- It never mutates cluster state.
- Pair it with
warnlabels for the fullest picture.
Intermediate Examples
8. Exempt a system workload cleanly
Some agents legitimately need more than restricted allows.
apiVersion: v1
kind: Namespace
metadata:
name: kube-agents
labels:
pod-security.kubernetes.io/enforce: privileged- Isolate privileged workloads in their own namespace, not the app namespace.
- This keeps the strict profile intact everywhere else.
- Combine with tight RBAC so few identities can deploy there.
- Prefer a scoped exemption over weakening a shared profile.
9. Deploy-level compliance
The same fields work on a Deployment through the pod template.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels: { app: api }
template:
metadata:
labels: { app: api }
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: registry.example.com/api@sha256:abc123
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]- The
securityContextlives in the pod template, so every replica inherits it. runAsUser: 10001pins a concrete non-root UID.- The digest-pinned image makes the deploy reproducible and tamper-evident.
readOnlyRootFilesystem: trueblocks writes to the container filesystem.
10. Layer a policy engine on top
PSA cannot express custom rules, so add a validating policy for those.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-requests-limits
spec:
validationFailureAction: Enforce
rules:
- name: check-resources
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "CPU and memory requests and limits are required."
pattern:
spec:
containers:
- resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"- PSA handles the standard profile; Kyverno handles rules it cannot, like required resources.
validationFailureAction: Enforcerejects non-compliant pods.- Run new policies in
Auditfirst, then switch toEnforce. - This two-tool split is the common production pattern.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).