Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for DevOps Security & Hardening By James Joyner IV · · 8 min read

Pod Security Standards in Practice: Hardening Workloads at Admission Time

Most pods run with privileges they never use. Pod Security Standards close that gap. Here's how to enforce restricted profiles without breaking your apps.

  • #security
  • #hardening
  • #kubernetes
  • #pod-security
  • #admission-control
  • #containers

Pull up a random pod spec in most clusters and you’ll find it running as root, with a writable root filesystem, the ability to escalate privileges, and a fistful of Linux capabilities it never touches. None of that is needed for the app to work — it’s just the default, and defaults are how attackers get an easy time after they pop a container. A workload running as root with CAP_SYS_ADMIN is a container escape waiting for a kernel bug.

Pod Security Standards (PSS) are Kubernetes’ answer: a set of profiles that constrain what pods may do, enforced at admission so a non-compliant pod never schedules. Here’s how to actually adopt them without a wave of broken deployments.

The three profiles

PSS defines three levels, and you should know what each one means:

  • Privileged — no restrictions. This is “we gave up.” Reserve it for genuine infrastructure workloads (some CNI and storage components) that truly need host access.
  • Baseline — blocks the obviously dangerous stuff: no privileged containers, no host namespaces, no host path mounts, limited capabilities. This stops the worst escapes with minimal app friction.
  • Restricted — the hardened target: must run as non-root, no privilege escalation, drop ALL capabilities, seccomp profile required, read-only considerations. This is what most application workloads should run under.

The realistic goal for app namespaces is restricted. Baseline is the floor; privileged is an exception you justify in writing.

Built-in enforcement via namespace labels

The Pod Security Admission controller is built into Kubernetes — no install needed. You opt namespaces in with labels, and crucially you can run all three modes at once:

apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/audit: restricted

enforce blocks violations, warn returns a warning to whoever applied the manifest, and audit writes to the audit log. The migration trick is to set warn and audit to restricted before you set enforce. You’ll see exactly what would break, in the logs and in kubectl apply output, without breaking anything yet.

What a restricted-compliant pod looks like

Most of the work is filling in the securityContext that should have been there all along:

spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: api
      image: ghcr.io/acme/api@sha256:...
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]

Each line removes an attacker capability: runAsNonRoot means a container escape lands as an unprivileged user, allowPrivilegeEscalation: false blocks setuid tricks, drop: ALL strips capabilities the app doesn’t use, readOnlyRootFilesystem stops an attacker writing a payload to disk, and RuntimeDefault seccomp blocks dangerous syscalls. None of it changes what a healthy app does — it only removes room to maneuver after a compromise.

The breakages you’ll actually hit

Flipping enforce: restricted exposes the assumptions baked into your images. The common ones:

  • Images built to run as root. The container writes to / or binds a low port. Fix the image (run as non-root, use an unprivileged port) rather than weakening the policy. This is the most common blocker and it’s the right thing to fix.
  • readOnlyRootFilesystem breaks apps that write temp files. Mount an emptyDir at the paths they need to write (/tmp, cache dirs) instead of making the whole filesystem writable.
  • Sidecars and init containers. They need the same securityContext. The one you forgot is the one that fails admission.
  • Legitimate privileged workloads. Your CNI agent or a node-exporter genuinely needs host access. Put those in their own namespace labeled privileged — don’t drop the whole cluster to baseline to accommodate three pods.

Going beyond built-in PSS

The built-in admission controller is namespace-level and somewhat coarse — it can’t do exceptions for one pod, can’t mutate manifests to fix them, and can’t express custom rules. When you outgrow it, a policy engine like Kyverno or Gatekeeper enforces the same standards with more flexibility: per-workload exceptions, mutating manifests to inject the right securityContext automatically, and custom rules built on top of the baseline. Both ship the PSS profiles as ready-made policies, so you’re extending the standard rather than reinventing it. Mutation is the underrated feature here — instead of rejecting a pod that’s missing allowPrivilegeEscalation: false, you can inject the safe default and let it through.

The rollout that sticks

The sequence that has worked for me every time:

  1. Label namespaces with warn/audit: restricted, enforce nothing.
  2. Collect violations for a week from warnings and audit logs.
  3. Fix images and specs — non-root, dropped caps, emptyDir for writable paths.
  4. Move infra workloads that genuinely need privilege into their own labeled namespaces.
  5. Flip enforce: baseline cluster-wide first as a safety floor.
  6. Promote app namespaces to enforce: restricted one at a time.

The whole thing is a measurement-then-enforcement exercise. Teams that try to jump straight to enforced-restricted break a dozen deploys and give up. Teams that watch the warnings first fix the real issues and make it stick.

What you end up with is a fleet where a compromised container is a frustrated attacker: no root, no capabilities, no writable disk, no privilege escalation. The kernel bug they were counting on for an escape now has far less to work with.

More workload hardening lives in our security and hardening guides. When a PR adds a privileged: true or loosens a securityContext, the AI code review assistant helps flag the quiet downgrade before it ships.

Profile and securityContext examples are illustrative. Roll out in warn/audit mode and validate against your own workloads before enforcing restricted profiles in production.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.