Free lesson · CKAD Prep Program · Module 5: Environment, configuration and security
Drop Privileges with Security Contexts
Default containers run as root with a full capability set, which turns any escape into a host compromise. Security contexts strip that down to exactly what the app needs.
12 min reading
Objectives
- Run containers as non-root users with explicit IDs
- Drop capabilities and forbid privilege escalation per container
- Set pod-level fsGroup plus sysctls where the app needs them
- Read a denied syscall back to the missing context field
Why this matters
Container breakout severity equals container privilege, so least privilege is the cheapest security control in the cluster. The CKAD tests the fields directly because they are pure YAML with immediate enforceable effects: runAsUser, capabilities, readOnlyRootFilesystem, allowPrivilegeEscalation. Exams love them for the same reason attackers hate them.
Concepts
Pod-level context sets shared defaults: runAsUser plus runAsGroup for all containers, fsGroup so mounted volumes arrive group-writable, and selected sysctls. Container-level context overrides per container and carries the sharp tools: capabilities drop (ALL, then add back only NET_BIND_SERVICE or whatever the app proves it needs), allowPrivilegeEscalation false (blocks setuid paths to root), readOnlyRootFilesystem true (with emptyDir mounts for the scratch paths the app genuinely writes), and runAsNonRoot true (refuses images that only run as uid 0).
Denials read back cleanly: permission errors on writes mean the filesystem or user is wrong, capability errors name the missing capability in audit or dmesg, and CrashLoops at start with runAsNonRoot set mean the image demands root and must be fixed or replaced.
Worked example
An nginx-shaped app, stripped to its needs:
securityContext:
runAsUser: 10001
runAsGroup: 30001
fsGroup: 30001
containers:
- name: web
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop: [ALL]
add: [NET_BIND_SERVICE]
volumeMounts:
- { name: tmp, mountPath: /tmp }
- { name: cache, mountPath: /var/cache/nginx }
volumes:
- { name: tmp, emptyDir: {} }
- { name: cache, emptyDir: {} }kubectl exec as uid 10001 proves the identity; writing outside the mounts fails by design; binding port 80 works through the single kept capability. Every allowance traces to a need, nothing to habit.
Exam notes
readOnlyRootFilesystem breaks apps that write config at boot: add the exact scratch mounts instead of dropping the flag. Dropping ALL capabilities kills features silently (binding low ports, raw sockets, chown): add back one at a time from the error. And runAsUser without fsGroup leaves volume writes owned by root: pair them whenever the app writes to mounts.
Quick check
A 4-question self-check. Your answers stay on your device, are never stored or sent. Only a passing score (for example 3 of 4) is saved, and only to unlock lesson progress: at least 3 correct is required before a lesson counts as complete. It never counts toward any certification assessment.
Lesson feedback
No published feedback yet.
Log in and complete the lesson to leave feedback.
Exercise
Harden a fictional root-running app manifest with pod plus container contexts (non-root user, dropped caps with one justified add-back, read-only root with scratch mounts, no escalation). Show the exec proof of identity plus one designed denial with its meaning.
Pass criteria
Contexts complete at both levels with every allowance justified; exec proves non-root; the denial output is quoted with the correct field-level reading.