Back to Blog
APPSEC

Kubernetes Pod Security: From Defaults to Production-Ready (2026)

18 min readEmre Sakarya
Pod::restricted

A Kubernetes Pod created with vanilla defaults — no securityContext, no resource limits, the default service account, and no explicit Pod Security Standards profile — has a runtime privilege profile that would have been considered alarming a decade ago and remains alarming in 2026 despite a decade of community guidance to the contrary. The defaults run containers as root, with the full Linux capability set, mounting service account tokens automatically, with no resource limits and no seccomp filtering. Each of these is a separate hardening opportunity, and a production-ready Pod specification is the combination of all of them applied deliberately. This deep dive walks the Pod hardening patterns that distinguish production-ready manifests from out-of-the-box defaults, the Pod Security Standards (PSS) profiles and how to enforce them, the securityContext fields that actually matter, and the policy-as-code layer (OPA Gatekeeper, Kyverno) that mediates what reaches the cluster. The broader cloud-native security framing this fits inside is covered in the cloud-native security practices pillar.

The Default Pod's Security Posture — and Why It Is Wrong

Kubernetes was designed for compatibility with the broadest possible range of workloads, which produced defaults optimized for "will this Pod start?" rather than "is this Pod safe to run?" The default Pod specification, written as the kind of YAML a tutorial typically produces, has six security-relevant properties that are worse than they need to be in production.

Containers run as root. Without an explicit runAsUser or runAsNonRoot setting, containers run with whatever USER the image's Dockerfile specified, which for most popular base images is root (UID 0). A root process inside a container has substantially more attack surface than a non-root process — Linux capabilities affect privilege, mounted host filesystems may be writable, and container-escape vulnerabilities are more impactful from a root-owned process. The hardening: every production Pod sets runAsNonRoot: true and runAsUser: <non-zero UID> explicitly.

The full Linux capability set is granted. Containers inherit a default Linux capability set that includes capabilities most application workloads don't need (CHOWN, KILL, NET_RAW, SETUID, SETGID, and a dozen more). The hardening: drop the entire capability set with capabilities.drop: ["ALL"] and add back only the specific ones the workload needs. Most application workloads need none; system-level workloads (CNI plugins, network proxies) need a tight subset.

The service account token is mounted automatically. Every Pod gets the default service account in its namespace mounted as a credential file, accessible from any process inside the container. If the default service account has any cluster permissions — and many do — a compromised Pod gains those permissions. The hardening: automountServiceAccountToken: false on every workload that doesn't need to talk to the Kubernetes API, plus an explicit per-workload service account with minimum-necessary RBAC for workloads that do.

No resource limits. Without CPU and memory limits, a misbehaving Pod can consume cluster resources unbounded — affecting noisy-neighbor workloads, causing eviction cascades, and potentially producing a cluster-wide outage from a single workload's bug. The hardening: explicit resources.limits.cpu and resources.limits.memory on every container, sized appropriately for the workload's actual usage plus burst headroom.

Default seccomp profile. The default seccomp profile (RuntimeDefault) is a reasonable baseline that blocks the most dangerous syscalls; the unconfined profile (Unconfined) is the historical Kubernetes default that allows all syscalls. The hardening: explicit seccompProfile.type: RuntimeDefault on every Pod (or a workload-tuned profile for stricter cases).

Privileged escalation allowed. Without allowPrivilegeEscalation: false, processes inside the container can gain additional privileges through setuid binaries. The hardening: always set this to false unless a specific workload requires the contrary.

Pod Security Standards (PSS) — The Three Profiles

The Pod Security Standards define three profiles — Privileged, Baseline, and Restricted — that constrain what Pod configurations are allowed. PSS replaced the deprecated PodSecurityPolicy mechanism in Kubernetes 1.25 and is now the canonical built-in mechanism for enforcing Pod-level security policy.

Privileged profile. Permissive policy with no significant restrictions. Suitable for system-level workloads that legitimately need host access — CNI plugins, container runtime interfaces, storage drivers, observability agents that need /proc access. Tenant workloads should never run in the privileged profile.

Baseline profile. Minimally restrictive policy that prevents known privilege escalations. Blocks privileged containers, host namespace sharing, host network access, hostPath volumes, and a handful of other patterns. The baseline profile is the minimum-viable production hardening; below this, the cluster's tenant isolation is essentially nominal.

Restricted profile. Heavily restricted policy enforcing current Pod hardening best practices. Requires non-root user, drops all capabilities, requires seccomp RuntimeDefault, disallows privilege escalation, and several other tightenings. The restricted profile is what production tenant workloads should target; workloads that genuinely cannot run under restricted are candidates for review before being granted exceptions.

The enforcement mechanism is the Pod Security Admission controller, configured per namespace via labels:

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

The three modes — enforce, audit, warn — let the cluster gradually adopt a profile: warn surfaces violations to the user creating the Pod, audit logs them to the cluster's audit log, and enforce rejects the Pod creation entirely. The graduation path is warn → audit → enforce, applied one namespace at a time as workloads are confirmed compliant.

The securityContext Fields That Matter

The Pod's securityContext is the YAML stanza where most of the hardening actually expresses itself. A production-ready Pod specification's security-relevant fields, in the order they typically appear:

apiVersion: v1
kind: Pod
metadata:
  name: app
  namespace: production-workloads
spec:
  automountServiceAccountToken: false
  serviceAccountName: app-sa
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    runAsGroup: 10001
    fsGroup: 10001
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: registry.example.com/app@sha256:abc123...
    imagePullPolicy: Always
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]
      runAsNonRoot: true
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 512Mi
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir:
      sizeLimit: 100Mi

The fields worth noting individually. runAsNonRoot: true at both the Pod and container level enforces non-root execution even if the image's USER directive specifies root. readOnlyRootFilesystem: true makes the container's root filesystem read-only, forcing the workload to write only to explicitly-mounted writable volumes (typically a small tmpfs for /tmp). capabilities.drop: ["ALL"] drops every capability; capabilities can be added back via capabilities.add for the specific ones a workload needs. fsGroup sets the group ownership of mounted volumes, supporting non-root file access on shared volumes.

The image reference uses a digest (@sha256:...) rather than a mutable tag. Tag-based references are vulnerable to tag mutation attacks where an attacker (or, more commonly, an accidental retag) changes what the tag points to between Pod template authoring and actual pull. Digest references pin the image content cryptographically; the cluster pulls exactly the image whose digest was specified, regardless of what the tag now points to.

NetworkPolicies as Pod-Level Defense

Pod security hardening at the runtime layer is necessary but not sufficient. The complementary defense is Kubernetes network policies — also written informally as "network policies" — which constrain what the Pod can talk to at the network layer. The default Kubernetes networking behavior — every Pod can talk to every other Pod across all namespaces — is wrong for any production cluster. The right default with network policies is deny-all ingress and egress, with explicit allow rules for the specific traffic patterns each workload actually needs.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production-workloads
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: app-allow
  namespace: production-workloads
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53

The pattern: a default-deny NetworkPolicy in every namespace catches all traffic not explicitly allowed, then per-workload NetworkPolicies define the specific traffic each workload needs. The api Pods can receive traffic from frontend Pods on 8080 and make outbound connections to database Pods on 5432 and DNS in kube-system on 53. Anything else is blocked.

NetworkPolicy enforcement requires a CNI plugin that supports it — Cilium, Calico, Weave Net, Antrea. Clusters running CNI plugins without NetworkPolicy enforcement (vanilla Flannel, some default cloud-provider CNIs) accept and store NetworkPolicy resources without actually enforcing them, which is the worst kind of failure mode because the cluster operator believes policies are in effect when they are not. Verify enforcement, not configuration.

Policy as Code — OPA Gatekeeper and Kyverno

Pod Security Standards cover a fixed set of patterns; production clusters typically have additional policies they want to enforce — image registry allow-lists, label requirements, resource limit minimums, latest-tag prohibitions. OPA Gatekeeper and Kyverno are the two dominant policy-as-code admission controllers in 2026, both functioning as Kubernetes admission webhooks that mediate every resource creation against a policy library.

OPA Gatekeeper. The CNCF-graduated project that brings Open Policy Agent's Rego policy language to Kubernetes admission control. Policies are written as ConstraintTemplate resources (Rego logic) plus Constraint resources (parameter values). Gatekeeper's strength is the flexibility and power of Rego; its weakness is the operational complexity of authoring and debugging Rego policies. Production deployments typically use a library of pre-built policy templates (the Gatekeeper Policy Library) and write only domain-specific policies from scratch.

Kyverno. The CNCF-incubating alternative that uses YAML-based policy definitions rather than Rego. Policies are simpler to write but less expressive than Rego for complex cases. Kyverno also supports mutation policies (modifying resources at admission time to enforce conventions) and image-signature verification (built-in Sigstore Cosign integration) that Gatekeeper doesn't natively cover.

The choice between the two depends on team preference. Teams with Rego experience or complex policy requirements tend toward Gatekeeper; teams optimizing for ease of use and built-in image-signature verification tend toward Kyverno. Many production deployments run both — Gatekeeper for complex policy, Kyverno for image verification and resource mutation.

An example Kyverno policy that enforces several Pod hardening properties:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-pod-hardening
spec:
  validationFailureAction: Enforce
  rules:
  - name: require-non-root-and-readonly-rootfs
    match:
      any:
      - resources:
          kinds: ["Pod"]
    validate:
      message: "Containers must run as non-root with readOnlyRootFilesystem"
      pattern:
        spec:
          containers:
          - securityContext:
              runAsNonRoot: true
              readOnlyRootFilesystem: true
              allowPrivilegeEscalation: false
  - name: require-resource-limits
    match:
      any:
      - resources:
          kinds: ["Pod"]
    validate:
      message: "All containers must specify CPU and memory limits"
      pattern:
        spec:
          containers:
          - resources:
              limits:
                memory: "?*"
                cpu: "?*"

The pattern above enforces two of the hardening properties from earlier — non-root + readOnlyRootFilesystem + allowPrivilegeEscalation, and resource limits. A more comprehensive policy library would add image registry allow-lists, image signature verification, label requirements, and the rest of the production-hardening checklist. The Gatekeeper Policy Library and Kyverno's documentation both ship example policies covering most of this surface; production deployments typically extend these with organization-specific additions rather than writing from scratch.

The Graduation Path — From Default Cluster to Production-Ready

For a cluster currently running with default Pod settings, the graduation path to production-ready hardening has four stages. Each stage builds on the previous one; skipping ahead produces operational pain that drives teams to abandon hardening efforts.

Stage 1 — Visibility. Apply Pod Security Standards at audit and warn level across all namespaces. Watch the audit logs and warnings accumulate. This stage produces no enforcement and no application breakage; it produces an inventory of which workloads would be rejected under restricted-profile enforcement. Stage 1 typically runs for two to four weeks depending on workload diversity.

Stage 2 — Per-workload hardening. Working through the audit-log inventory, update each workload's manifest to meet the restricted profile. The work is mostly mechanical — add securityContext, drop capabilities, set non-root user, add resource limits — but requires per-workload validation that the workload still functions. Stage 2 is where most of the calendar time goes; budget four to twelve weeks for a non-trivial workload count.

Stage 3 — Enforcement. Switch PSS from audit to enforce, namespace by namespace. Each switch should be preceded by a clean audit log for that namespace's workloads. The switch itself is a single label change and a Pod re-creation event; the discipline is in confirming readiness namespace by namespace, not in the switch itself.

Stage 4 — Policy expansion. Add policy-as-code (Kyverno or Gatekeeper) for the patterns PSS doesn't cover — registry allow-lists, image signature verification, label requirements. By this stage, the cluster has restricted-profile PSS enforcement plus additional policy-as-code, NetworkPolicy default-deny, per-workload service accounts, and the broader hardening checklist applied consistently.

The full graduation typically takes a quarter for a mid-size cluster, longer for clusters with diverse legacy workloads. Teams that try to compress this into a sprint usually break production workloads and produce political resistance that delays subsequent hardening efforts; teams that take the staged approach reach the same end state with substantially less operational friction.

Where Pod Hardening Fits in ASVS Verification

Pod-level hardening evidence flows into the broader ASVS 5.0 verification work via chapter V13 Configuration, which contains the deployment configuration requirements that Pod Security Standards and policy-as-code admission rules satisfy. The verification pattern: PSS profile enforcement at the restricted level produces V13 evidence; Kyverno or Gatekeeper policy library coverage produces additional V13 evidence; NetworkPolicy default-deny produces evidence for V8 Authorization and V12 Secure Communication; per-workload service account RBAC produces evidence for V8 Authorization.

This integration matters operationally because the same hardening work that produces a defensible Pod security posture also produces the compliance evidence the broader application security program needs. PCI DSS Requirement 6.4 (build process safeguards), SOC 2 CC8.1 (change management), and EU CRA Annex I.1 (security-by-design) all consume the same Pod-hardening evidence as input. Programs that run Pod hardening as a separate discipline from compliance evidence collection do redundant work; programs that align them collect compliance evidence as a byproduct of operational hardening.

The broader cloud-native security framing — image signing, supply-chain attestations, service mesh, runtime security — is covered in the cloud-native security practices pillar. Pod hardening is the cluster-layer component of that broader stack; this post covered it in operational detail, the pillar covers how it connects to the layers above and below.

Kubernetes Pod security: questions developers ask

What is the difference between Pod Security Standards Baseline and Restricted?

Baseline blocks privileged containers, host namespace sharing, host network access, and a handful of other known privilege escalations — the minimum-viable production hardening. Restricted requires non-root user, drops all capabilities, requires seccomp RuntimeDefault, disallows privilege escalation, and enforces current Pod hardening best practices. Production tenant workloads should target Restricted; Baseline is the floor below which tenant isolation is essentially nominal.

How do I enforce Pod Security Standards?

Configure the Pod Security Admission controller via namespace labels: pod-security.kubernetes.io/enforce: restricted enforces the profile; audit and warn levels surface violations without rejecting Pods. The graduation path is warn → audit → enforce, applied one namespace at a time as workloads are confirmed compliant.

Should I use OPA Gatekeeper or Kyverno?

Gatekeeper uses Rego policy language — powerful but complex. Kyverno uses YAML-based policies — simpler to write, less expressive for complex cases. Kyverno also has built-in Sigstore Cosign integration and mutation policies. Teams with Rego experience tend toward Gatekeeper; teams optimizing for ease of use tend toward Kyverno. Many production deployments run both — Gatekeeper for complex policy, Kyverno for image verification.

Do I need NetworkPolicies if I have a service mesh?

Yes. NetworkPolicies enforce IP/port-level constraints at the network layer; service mesh enforces identity-based authorization at the application layer. They are defense-in-depth, not substitutes. A workload with no service mesh authorization but tight NetworkPolicies has weak protection; a workload with strong mesh authorization but no NetworkPolicies leaks information about who can talk to whom regardless of mesh policy.

How long does it take to make Pods production-ready?

For a mid-size cluster, a quarter is realistic. Stage 1 (audit-mode visibility) two to four weeks. Stage 2 (per-workload hardening) four to twelve weeks depending on workload count and diversity. Stage 3 (enforce-mode rollout) one to two weeks. Stage 4 (policy-as-code expansion) two to four weeks. Compressing this timeline typically breaks workloads and produces resistance that delays subsequent hardening.

Why is the Kubernetes default Pod insecure?

The defaults were designed for "will this Pod start?" compatibility rather than "is this Pod safe?" production posture. The default Pod runs as root, has the full Linux capability set, automatically mounts the service account token, has no resource limits, and allows privilege escalation. Each of these is a separate hardening opportunity that production deployments need to address explicitly.