Pod Security Best Practices
How to Use This List
This is the checklist for hardening pods in production, grouped from admission policy down to the image and network.
Treat the groups as a rollout order. Get admission enforcement and securityContext right first, then image supply chain, then runtime and network.
Adopt them with warn and audit before you enforce, so you fix workloads instead of blocking deploys. Everything here targets Kubernetes 1.36.2 with containerd on the nodes.
A - Admission Enforcement
Enforce restricted in production namespaces. Label each app namespace with pod-security.kubernetes.io/enforce: restricted so violating pods are rejected before scheduling.
Dry-run with warn and audit first. Set warn and audit to the target profile, read the violations from your CI logs and audit trail, then raise enforce.
Pin the enforce version deliberately. Use enforce-version: latest to track the evolving profile, or pin a version like v1.36 when you need stable behavior across upgrades.
Isolate exceptions in their own namespaces. Put workloads that need privileged or baseline in dedicated namespaces with tight RBAC, rather than loosening a shared profile.
Add a policy engine for rules PSA cannot express. Layer Kyverno or OPA Gatekeeper on top of PSA for registry allowlists, required labels, and mandatory resource limits.
B - Container securityContext
Run as non-root and pin the UID. Set runAsNonRoot: true with a concrete runAsUser so the kubelet refuses any image that would run as UID 0.
Drop all capabilities, add back only what is proven. Use capabilities.drop: ["ALL"] and add a single capability like NET_BIND_SERVICE only with evidence it is needed.
Disable privilege escalation. Set allowPrivilegeEscalation: false on every container so no_new_privs is applied.
Make the root filesystem read-only. Set readOnlyRootFilesystem: true and mount emptyDir at the few writable paths the app needs, such as /tmp.
Never use privileged containers for app workloads. Reserve privileged: true, host namespaces, and hostPath mounts for trusted infrastructure pods only.
C - Kernel Confinement
Enable RuntimeDefault seccomp everywhere. Set seccompProfile.type: RuntimeDefault at the pod level to block a large class of dangerous syscalls with one line.
Author custom seccomp profiles for high-risk workloads. Record a least-privilege allowlist with the Security Profiles Operator rather than hand-writing JSON.
Apply AppArmor or SELinux where the platform supports it. Use the appArmorProfile field on Debian/Ubuntu/SUSE nodes, or seLinuxOptions on RHEL-family nodes, starting in complain mode.
Use a sandboxed runtime for untrusted code. Route hostile or multi-tenant pods through a gVisor or Kata RuntimeClass to add a real kernel boundary.
D - Image Supply Chain
Build minimal, non-root images. Start from distroless or slim bases and declare a non-root USER in the Dockerfile so pods default to non-root.
Pin images by digest. Reference image@sha256:... rather than mutable tags so deploys are reproducible and tamper-evident.
Scan images and block on critical findings. Run Trivy or Grype in CI and fail the pipeline on fixable critical CVEs.
Sign and verify provenance. Sign images with cosign and enforce signature verification at admission so only trusted images run.
Remember containerd runs the pods. Docker Engine with BuildKit builds the images; containerd via the CRI runs them on nodes, so harden the image, not the Docker daemon.
E - Network and Identity
Default-deny network traffic. Apply a NetworkPolicy that denies all ingress and egress per namespace, then allow only required flows.
Scope RBAC to least privilege. Give each workload a dedicated ServiceAccount with only the permissions it needs, and avoid cluster-admin bindings.
Disable unused ServiceAccount tokens. Set automountServiceAccountToken: false on pods that never call the Kubernetes API.
Set resource requests and limits. Constrain CPU and memory so a compromised or runaway pod cannot starve the node.
When You Are Done
You should have restricted enforced on every production namespace, a compliant securityContext on every workload, and RuntimeDefault seccomp cluster-wide.
Images should be minimal, non-root, digest-pinned, scanned, and signed, with a policy engine catching what PSA cannot.
Network should be default-deny with least-privilege RBAC. Verify the whole posture with kubectl apply --dry-run=server in CI so regressions never reach the cluster.
FAQs
Where do I start if I have none of this? Turn on image scanning, then set namespaces to warn: restricted, fix the workloads, then enforce: restricted. It sequences the least disruptive changes first.
PSA or a policy engine? Both. PSA gives you the standard profiles cheaply; Kyverno or Gatekeeper cover custom rules like registry allowlists and required resource limits.
How do I handle a workload that needs a capability? Add the single capability back with capabilities.add and document why. Do not drop the whole hardening because one syscall is needed.
Do these apply to Jobs and CronJobs too? Yes. Any object with a pod template - Deployments, StatefulSets, DaemonSets, Jobs - carries the same securityContext and is evaluated by PSA.
How do I prove compliance? Use server-side dry run in CI, the audit PSA label for a violation trail, and periodic scans of running pods against the restricted profile.
Related
- The Layers of Pod Security
- Pod Security Basics
- securityContext
- seccomp & AppArmor
- Why Containers Aren’t a Security Boundary
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).