Overview and What You Will Learn
In this guide you will learn how to lock down Kubernetes pods at the runtime level using securityContext settings and Pod Security Standards (PSS). You will understand what each security setting does, how to apply security policies at the namespace level, and how to audit your cluster for insecure pods. By the end you will be able to harden any workload against the most common container escape and privilege escalation attacks.
Why This Matters in Production
A container running as root with no security restrictions is one kernel exploit away from compromising the entire node. At Razorpay or PhonePe handling financial transactions, a single compromised container that can escalate to root and access the host filesystem means customer data exposure. Pod Security Standards and securityContext settings are the specific Kubernetes controls that prevent this.
Core Principles
+------------------------------------------+| Pod Security Standards (PSS) | <- Namespace-level enforcement| Applied via namespace labels | Three levels available+------------------------------------------+ | v+------------------------------------------+| Privileged | <- No restrictions at all| Baseline | <- Blocks known privilege escalation| Restricted | <- Hardened β most secure+------------------------------------------+ | v+------------------------------------------+| securityContext | <- Pod/container-level controls| runAsNonRoot, readOnlyRootFilesystem | Fine-grained security settings| allowPrivilegeEscalation: false |+------------------------------------------+Detailed Step-by-Step Practical Lab
Step 1: Applying Pod Security Standards to a Namespace
Pod Security Standards are enforced by adding labels to namespaces. Kubernetes then automatically checks every pod created in that namespace against the chosen profile.
# Apply the Restricted PSS to a namespace β strictest modekubectl label namespace production \ 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# Or define it in the namespace manifest for GitOps workflowsapiVersion: v1kind: Namespacemetadata: name: production labels: pod-security.kubernetes.io/enforce: restricted # Blocks non-compliant pods pod-security.kubernetes.io/warn: restricted # Shows warning in kubectl output pod-security.kubernetes.io/audit: restricted # Logs violations to audit log# Check which PSS level is applied to a namespacekubectl get namespace production -o yaml | grep pod-security # Test if a pod would be allowed in the namespace without actually deploying itkubectl apply --dry-run=server -f deployment.yaml -n production# If the pod violates PSS, you see the error before it hits the clusterStep 2: Securing a Container with securityContext
# deployment.yaml β production-hardened pod spec for a payments APIapiVersion: apps/v1kind: Deploymentmetadata: name: payments-api namespace: productionspec: template: spec: # Pod-level securityContext β applies to ALL containers securityContext: runAsNonRoot: true # Prevent running as root user runAsUser: 1000 # Run as UID 1000 (non-root) runAsGroup: 1000 # Run as GID 1000 fsGroup: 2000 # Files written to volumes use GID 2000 seccompProfile: type: RuntimeDefault # Apply default seccomp filter # Blocks dangerous syscalls containers: - name: payments-api image: registry.razorpay.in/payments-api:v3.1.0 # Container-level securityContext β overrides pod level for this container securityContext: allowPrivilegeEscalation: false # Cannot gain more privileges than parent readOnlyRootFilesystem: true # Container filesystem is read-only # Prevents writing malware to disk capabilities: drop: - ALL # Drop ALL Linux capabilities add: - NET_BIND_SERVICE # Only add back what is actually needed # (allows binding to port 80 as non-root) volumeMounts: - name: tmp-dir mountPath: /tmp # Mount writable /tmp since root fs is read-only - name: cache-dir mountPath: /app/cache volumes: - name: tmp-dir emptyDir: {} # Temporary writable directory - name: cache-dir emptyDir: {}Step 3: What Each securityContext Setting Does
+------------------------------------------+| runAsNonRoot: true | <- Kubernetes rejects the pod if the| | container image runs as UID 0 (root)+------------------------------------------+ +------------------------------------------+| readOnlyRootFilesystem: true | <- Container cannot write to its own| | filesystem β malware cannot be written| | even if the container is compromised+------------------------------------------+ +------------------------------------------+| allowPrivilegeEscalation: false | <- setuid binaries cannot gain root| | even if they exist in the image| | (blocks sudo, su, passwd etc)+------------------------------------------+ +------------------------------------------+| capabilities.drop: [ALL] | <- Removes all Linux capabilities| | like NET_ADMIN, SYS_ADMIN etc| | Limits what a compromised container| | can do on the host+------------------------------------------+ +------------------------------------------+| seccompProfile: RuntimeDefault | <- Applies a syscall filter that blocks| | dangerous kernel calls like| | ptrace, keyctl, mount+------------------------------------------+Step 4: Auditing Existing Pods for Security Violations
# Find all pods running as root in the clusterkubectl get pods -A -o json | \ jq '.items[] | select( .spec.containers[].securityContext.runAsUser == 0 or .spec.securityContext.runAsUser == 0 or (.spec.containers[].securityContext.runAsNonRoot == false) ) | .metadata.name + " in " + .metadata.namespace' # Find all pods with allowPrivilegeEscalation not explicitly set to falsekubectl get pods -A -o json | \ jq '.items[] | select( .spec.containers[].securityContext.allowPrivilegeEscalation != false ) | .metadata.name' # Use kubectl-score to get a security score for all workloads# Install: https://kube-score.comkubectl score deployment payments-api -n production # Simulate PSS enforcement without blocking pods β use warn mode firstkubectl label namespace production \ pod-security.kubernetes.io/warn=restricted# Now deploy and watch kubectl output for PSS warnings# Fix violations before switching from warn to enforceStep 5: Fixing Common PSS Violation Errors
# Error: "pods violates PodSecurity 'restricted': allowPrivilegeEscalation != false"# Fix: add to container securityContext:securityContext: allowPrivilegeEscalation: false # Error: "pods violates PodSecurity 'restricted': unrestricted capabilities"# Fix: drop all capabilitiessecurityContext: capabilities: drop: [ALL] # Error: "pods violates PodSecurity 'restricted': runAsNonRoot != true"# Fix: add to pod securityContext:securityContext: runAsNonRoot: true runAsUser: 1000 # Error: "pods violates PodSecurity 'restricted': seccompProfile"# Fix: add to pod securityContext:securityContext: seccompProfile: type: RuntimeDefaultProduction Best Practices & Common Pitfalls
- Apply
warnmode to all namespaces first, then fix violations, then switch toenforce. Switching directly toenforceon a live namespace without auditing first can block critical production pods immediately. - Use
readOnlyRootFilesystem: trueon every container and mount explicitemptyDirvolumes for directories the app needs to write to (logs, temp files, cache). This is a minor inconvenience to configure but provides strong protection against post-exploitation persistence.
COMMON MISTAKE / WARNING**Common Mistake:** Setting `runAsNonRoot: true` at the pod level without verifying the container image actually supports running as non-root. Many official images like nginx default to running as root. The pod will be rejected at admission with a cryptic error. Always test with `docker run --user 1000` locally before setting runAsNonRoot in production.
Quick Reference & Troubleshooting Commands
| Command | Purpose |
|---|---|
kubectl label namespace <ns> pod-security.kubernetes.io/enforce=restricted |
Apply Restricted PSS |
kubectl get namespace <ns> -o yaml | grep pod-security |
Check PSS labels on namespace |
kubectl apply --dry-run=server -f pod.yaml |
Test PSS compliance without deploying |
kubectl get pods -A -o json | jq '...' |
Audit pods for security violations |