Security context and container hardening
Security context and container hardening
A container is a process on the node. The security context decides how much of the node that process can touch.
spec:
securityContext: # Pod level - applies to all containers
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile: { type: RuntimeDefault }
containers:
- name: app
image: nginxinc/nginx-unprivileged:1.27 # listens on 8080
securityContext: # container level - wins over Pod level
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- { name: tmp, mountPath: /tmp } # writable scratch space
volumes:
- { name: tmp, emptyDir: {} }
| Setting | Protects against |
|---|---|
runAsNonRoot / runAsUser | root in the container = root-owned files on the node if it escapes |
allowPrivilegeEscalation: false | setuid binaries, sudo inside |
readOnlyRootFilesystem: true | attacker dropping tools or changing the app |
capabilities.drop: [ALL] | raw sockets, changing ownership, binding ports < 1024 |
seccompProfile: RuntimeDefault | ~300 rarely needed syscalls |
never privileged: true, hostPID, hostNetwork, hostPath: / | these are the node |
Container hardening checklist
- Minimal base image (distroless, alpine, chiselled) - fewer CVEs, no shell.
- Image built to run as non-root (
USER 10001in the Dockerfile). - Pinned tag or digest (
@sha256:...), neverlatest. - Scanned in the pipeline (Trivy - Lab 33), signed (cosign).
- Resource limits set (Day 3) - a runaway container cannot starve the node.
- Only the ServiceAccount token it needs (usually none).
Many public images (nginx, httpd) expect root. Use their unprivileged variants or build your own.