K8s SecurityContext: Container Hardening
Configure Kubernetes SecurityContext for pods and containers. runAsNonRoot, readOnlyRootFilesystem, capabilities, seccomp profiles, and privilege escalation.
💡 Quick Answer: SecurityContext controls pod and container privileges. Minimum hardening:
runAsNonRoot: true,allowPrivilegeEscalation: false,readOnlyRootFilesystem: true,capabilities: {drop: ["ALL"]},seccompProfile: {type: RuntimeDefault}. Set at pod level for defaults, override per container when needed.
The Problem
By default, containers run with more privileges than necessary:
- Root user (UID 0) inside the container
- Write access to the container filesystem
- Inherits Linux capabilities (kill, chown, setuid)
- Can escalate privileges
- No seccomp filtering
The Solution
Full Hardened SecurityContext
apiVersion: v1
kind: Pod
metadata:
name: hardened-app
spec:
# Pod-level security (applies to all containers)
securityContext:
runAsNonRoot: true # Reject if image runs as root
runAsUser: 1000 # Run as UID 1000
runAsGroup: 1000 # Primary GID
fsGroup: 2000 # Volume GID
fsGroupChangePolicy: OnRootMismatch # Faster volume permission changes
seccompProfile:
type: RuntimeDefault # Container runtime's default seccomp
supplementalGroups: [3000] # Additional GIDs
containers:
- name: app
image: myapp:v2
# Container-level security (overrides pod-level)
securityContext:
allowPrivilegeEscalation: false # No setuid/setgid
readOnlyRootFilesystem: true # Immutable filesystem
capabilities:
drop: ["ALL"] # Remove all Linux capabilities
# add: ["NET_BIND_SERVICE"] # Only add what's needed
privileged: false # Never run privileged
# Writable directories via volumes
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}Pod vs Container SecurityContext
spec:
# Pod-level: applies to ALL containers (and init containers)
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:v2
# Container-level: overrides pod-level for THIS container
securityContext:
runAsUser: 2000 # Override pod's 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
- name: sidecar
image: fluent-bit:3.0
# Inherits pod-level: runAsNonRoot=true, runAsUser=1000
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]Common Capability Patterns
# Web server binding to port 80 (< 1024 needs capability)
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # Bind low ports
# Network debugging tools (tcpdump, iptables)
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_ADMIN", "NET_RAW"]
# Ping
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_RAW"]
# System monitoring (read-only)
securityContext:
capabilities:
drop: ["ALL"]
add: ["SYS_PTRACE"] # Read /proc of other processesReadOnly Filesystem with Writable Paths
containers:
- name: nginx
image: nginx:1.27
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: run
mountPath: /var/run
- name: cache
mountPath: /var/cache/nginx
- name: logs
mountPath: /var/log/nginx
volumes:
- name: tmp
emptyDir: {}
- name: run
emptyDir: {}
- name: cache
emptyDir: {}
- name: logs
emptyDir: {}fsGroup and Volume Permissions
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000 # All volume files get GID 2000
fsGroupChangePolicy: OnRootMismatch # Only change when needed (fast)
containers:
- name: app
image: myapp:v2
volumeMounts:
- name: data
mountPath: /data
# Files in /data will have group 2000
# Container process runs as uid=1000, gid=1000, groups=2000
volumes:
- name: data
persistentVolumeClaim:
claimName: app-dataVerify SecurityContext
# Check what user the container runs as
kubectl exec my-pod -- id
# uid=1000 gid=1000 groups=2000
# Check capabilities
kubectl exec my-pod -- cat /proc/1/status | grep -i cap
# CapBnd: 0000000000000000 (no capabilities)
# Check filesystem
kubectl exec my-pod -- touch /test
# touch: /test: Read-only file system ✅
# Check seccomp
kubectl exec my-pod -- cat /proc/1/status | grep Seccomp
# Seccomp: 2 (filter mode)Common Issues
Container fails with “permission denied”
Running as non-root but image writes to root-owned directories. Add emptyDir volumes for writable paths or rebuild image with correct ownership.
“readOnlyRootFilesystem” breaks application
App writes to filesystem (logs, temp files, PID files). Mount emptyDir volumes at each writable path.
fsGroup makes pod startup slow
Large volumes with many files — Kubernetes recursively chowns everything. Use fsGroupChangePolicy: OnRootMismatch (K8s 1.20+).
Best Practices
- Drop ALL capabilities then add only what’s needed — principle of least privilege
- runAsNonRoot + specific runAsUser — never run as root
- readOnlyRootFilesystem — prevents filesystem-based attacks
- allowPrivilegeEscalation: false — blocks setuid binaries
- seccompProfile: RuntimeDefault — filters dangerous syscalls
- Never set
privileged: trueunless absolutely required (and document why)
Key Takeaways
- SecurityContext controls UID/GID, capabilities, filesystem, and seccomp per pod/container
- Minimum hardening: runAsNonRoot, drop ALL caps, readOnly filesystem, no privilege escalation
- Pod-level sets defaults; container-level overrides per container
- Use emptyDir volumes for writable paths with readOnlyRootFilesystem
- fsGroup sets volume ownership for the pod’s group

Recommended
Kubernetes Recipes — The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book →Learn by Doing
CopyPasteLearn — Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses →🎓 Deepen Your Skills — Hands-on Courses
Courses by CopyPasteLearn.com — Learn IT by Doing
