In this episode, we'll discuss Kubernetes Pod Security Context for controlling Pod and container security settings. We'll learn how to run Pods as non-root users, set capabilities, enforce read-only filesystems, and best practices for Pod security.

Note
If you want to read the previous episode, you can click the Episode 36 thumbnail below
In the previous episode, we explored NetworkPolicy, which controls network traffic between Pods and external networks. Now we'll dive into Pod Security Context, which controls security settings for Pods and containers.
Note: Here I'll be using a Kubernetes Cluster installed through K3s.
Pod Security Context defines privilege and access control settings for a Pod or container. It controls aspects like user ID, group ID, filesystem permissions, and Linux capabilities. By properly configuring security context, you can significantly reduce the attack surface of your applications.
Pod Security Context is a set of security-related settings that apply to Pods and containers. It operates at the Linux kernel level and controls how containers interact with the host system.
1. Principle of Least Privilege
Run containers with minimal required permissions. Don't run as root unless absolutely necessary.
2. Prevent Privilege Escalation
Restrict containers from gaining additional privileges.
3. Enforce Read-Only Filesystems
Prevent containers from modifying the filesystem.
4. Control Linux Capabilities
Grant only necessary Linux capabilities instead of all root capabilities.
5. Compliance and Security Standards
Meet security compliance requirements like PCI-DSS, HIPAA, and SOC 2.
Security context can be applied at two levels:
Pod-Level - Applies to all containers in the Pod Container-Level - Applies to a specific container (overrides Pod-level settings)
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: app
image: myapp:latest
securityContext:
runAsUser: 2000
allowPrivilegeEscalation: falseapiVersion: v1
kind: Pod
metadata:
name: non-root-pod
spec:
securityContext:
runAsUser: 1000
containers:
- name: app
image: myapp:latestThis runs all containers in the Pod as user ID 1000.
apiVersion: v1
kind: Pod
metadata:
name: mixed-users
spec:
containers:
- name: app1
image: app1:latest
securityContext:
runAsUser: 1000
- name: app2
image: app2:latest
securityContext:
runAsUser: 2000Different containers run as different users.
apiVersion: v1
kind: Pod
metadata:
name: enforce-non-root
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latestThe runAsNonRoot: true setting prevents the container from running as root. If the image tries to run as root, the Pod will fail to start.
apiVersion: v1
kind: Pod
metadata:
name: group-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
containers:
- name: app
image: myapp:latestThis sets the primary group ID to 3000.
apiVersion: v1
kind: Pod
metadata:
name: fsgroup-demo
spec:
securityContext:
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}The fsGroup setting changes the group ownership of mounted volumes. All processes in the container are also part of this group.
apiVersion: v1
kind: Pod
metadata:
name: supplemental-groups
spec:
securityContext:
runAsUser: 1000
supplementalGroups: [4000, 5000]
containers:
- name: app
image: myapp:latestThe container process is a member of groups 4000 and 5000 in addition to the primary group.
apiVersion: v1
kind: Pod
metadata:
name: readonly-fs
spec:
containers:
- name: app
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: var-log
mountPath: /var/log
volumes:
- name: tmp
emptyDir: {}
- name: var-log
emptyDir: {}This makes the root filesystem read-only. The container can only write to mounted volumes.
Linux capabilities break down root privileges into smaller units. Instead of granting all root capabilities, you can grant only what's needed.
apiVersion: v1
kind: Pod
metadata:
name: drop-caps
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALLThis drops all Linux capabilities. The container runs with minimal privileges.
apiVersion: v1
kind: Pod
metadata:
name: add-caps
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEThis drops all capabilities but adds NET_BIND_SERVICE to allow binding to ports below 1024.
| Capability | Purpose |
|---|---|
| NET_BIND_SERVICE | Bind to ports < 1024 |
| NET_RAW | Use raw sockets |
| SYS_ADMIN | Various admin operations |
| SYS_TIME | Set system time |
| CHOWN | Change file ownership |
| DAC_OVERRIDE | Bypass file permissions |
| SETUID | Change user ID |
| SETGID | Change group ID |
apiVersion: v1
kind: Pod
metadata:
name: no-priv-escalation
spec:
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: falseThis prevents the container from gaining additional privileges through setuid or setgid binaries.
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
privileged: truePrivileged containers have access to all host devices and capabilities. Use only when absolutely necessary.
apiVersion: v1
kind: Pod
metadata:
name: selinux-demo
spec:
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
type: "spc_t"
containers:
- name: app
image: myapp:latestThis sets the SELinux context for the Pod.
Seccomp (Secure Computing) restricts which system calls a container can make.
apiVersion: v1
kind: Pod
metadata:
name: seccomp-default
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latestThis uses the container runtime's default seccomp profile.
apiVersion: v1
kind: Pod
metadata:
name: seccomp-custom
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: my-profile.json
containers:
- name: app
image: myapp:latestThis uses a custom seccomp profile stored on the node.
apiVersion: v1
kind: Pod
metadata:
name: secure-web
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: web
image: nginx:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/nginx
- name: run
mountPath: /var/run
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
- name: run
emptyDir: {}apiVersion: v1
kind: Pod
metadata:
name: secure-db
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
containers:
- name: postgres
image: postgres:15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
emptyDir: {}apiVersion: v1
kind: Pod
metadata:
name: minimal-privilege
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Problem: Container has unnecessary privileges.
# DON'T DO THIS - Runs as root
apiVersion: v1
kind: Pod
metadata:
name: root-pod
spec:
containers:
- name: app
image: myapp:latestSolution: Always specify runAsUser:
apiVersion: v1
kind: Pod
metadata:
name: non-root-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latestProblem: Container can gain additional privileges.
# DON'T DO THIS - Allows privilege escalation
apiVersion: v1
kind: Pod
metadata:
name: escalation-pod
spec:
containers:
- name: app
image: myapp:latestSolution: Disable privilege escalation:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: falseProblem: Container can modify the filesystem.
# DON'T DO THIS - Root filesystem is writable
apiVersion: v1
kind: Pod
metadata:
name: writable-pod
spec:
containers:
- name: app
image: myapp:latestSolution: Make root filesystem read-only:
apiVersion: v1
kind: Pod
metadata:
name: readonly-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Problem: Container has unnecessary capabilities.
# DON'T DO THIS - Container has all capabilities
apiVersion: v1
kind: Pod
metadata:
name: all-caps-pod
spec:
containers:
- name: app
image: myapp:latestSolution: Drop all capabilities and add only what's needed:
apiVersion: v1
kind: Pod
metadata:
name: minimal-caps-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEProblem: Container has full host access.
# DON'T DO THIS - Privileged container
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
privileged: trueSolution: Use specific capabilities instead:
apiVersion: v1
kind: Pod
metadata:
name: specific-caps-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
capabilities:
drop:
- ALL
add:
- NET_ADMINsecurityContext:
runAsNonRoot: true
runAsUser: 1000securityContext:
capabilities:
drop:
- ALLsecurityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmpsecurityContext:
allowPrivilegeEscalation: falseApply security context at the Pod level so all containers inherit it:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:latestUse Pod Security Policies (or Pod Security Standards in newer versions) to enforce security context requirements cluster-wide.
Verify that your security context settings work correctly:
# Check running user
kubectl exec -it pod-name -- id
# Check filesystem permissions
kubectl exec -it pod-name -- ls -la /
# Check capabilities
kubectl exec -it pod-name -- grep Cap /proc/self/statuskubectl describe pod pod-name
# Look for "Security Context" sectionkubectl get pod pod-name -o yaml
# Look for securityContext fieldkubectl exec -it pod-name -- id
# Output: uid=1000(user) gid=3000(group) groups=2000(fsgroup)kubectl exec -it pod-name -- grep Cap /proc/self/status
# Shows current capabilities1. Container Runtime Dependent
Some security context settings depend on the container runtime (Docker, containerd, etc.).
2. Host Kernel Limitations
Security context relies on Linux kernel features. Some settings may not work on all systems.
3. No Application-Level Security
Security context doesn't protect against application-level vulnerabilities.
4. Requires Proper Image Setup
The container image must be designed to run as non-root.
In episode 37, we've explored Pod Security Context in Kubernetes in depth. We've learned how to control security settings for Pods and containers, run as non-root users, manage capabilities, and enforce read-only filesystems.
Key takeaways:
By properly configuring Pod Security Context, you significantly reduce the attack surface of your applications and follow security best practices.
Note
If you want to continue to the next episode, you can click the Episode 38 thumbnail below