Tools · Containers and Kubernetes security · 05 / 20
Hardening pods with securityContext: runAsNonRoot, read-only root filesystem, and capabilities
Write a pod spec that meets the restricted standard, understand what each securityContext field changes, and fix the common reasons hardened containers fail to start.
The fields that matter
runAsNonRoot: true makes the kubelet refuse to start a container whose user is UID 0. runAsUser sets a specific UID. allowPrivilegeEscalation: false sets no_new_privs so setuid binaries cannot gain privileges. capabilities.drop: [ALL] removes Linux capabilities such as NET_RAW and CHOWN. readOnlyRootFilesystem: true makes the image filesystem immutable at runtime. seccompProfile type RuntimeDefault applies the runtime’s default system call filter.
A restricted-friendly spec
Pod-level settings apply to all containers; container-level settings override them. Writable paths the app needs, such as /tmp or a cache directory, become emptyDir volumes.
apiVersion: v1
kind: Pod
metadata:
name: api
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: ghcr.io/example/api:1.4.2
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Lab: harden nginx step by step
Deploy the stock nginx image with the hardened securityContext and watch it fail: it wants to run as root, bind port 80, and write to /var/cache/nginx. Switch to the nginxinc/nginx-unprivileged image, which listens on 8080 as a non-root user, and add emptyDir volumes for /tmp and /var/cache/nginx. Check the result with kubectl describe and confirm inside the container that id shows a non-zero UID and that touching a file in / fails.
kubectl exec api -- id kubectl exec api -- touch /test # expect: Read-only file system kubectl exec api -- grep Cap /proc/1/status
Common failures
CreateContainerConfigError with “container has runAsNonRoot and image will run as root” means the image has no USER instruction or uses a name instead of a UID; set runAsUser. Crash loops after enabling readOnlyRootFilesystem usually mean the app writes somewhere; read the logs for the path and mount an emptyDir there. Apps binding ports below 1024 need a higher port when capabilities are dropped.
Diagram
Default container versus hardened container
Each field removes a specific power.
User. Often root (UID 0)
Capabilities. Runtime default set, including NET_RAW
Filesystem. Writable image layer
User. UID 10001, runAsNonRoot
Capabilities. None (drop ALL)
Filesystem. Read-only, emptyDir for /tmp
Check yourself
1. A container fails with “image will run as root” under runAsNonRoot. What is the usual fix?
2. After enabling readOnlyRootFilesystem, the app crashes writing to /var/cache/app. What should you do?
3. Which field stops setuid binaries from gaining extra privileges?