Running Containers as Non-Root
The single most impactful security change you can make to most Pod specs costs exactly one line: runAsNonRoot: true. If the container image’s default user is root, the Pod will not start. That is the point. Kubernetes refuses to run it rather than silently allowing a known-risky configuration. This lesson covers the four fields that control process identity and explains how they interact.
runAsNonRoot: the enforcement gate
runAsNonRoot: true does not change which user the container runs as. It is a validation step. Before the container starts, Kubernetes checks whether the effective UID would be 0. If it would, the container is rejected with a security error. If the UID is anything other than 0, the container starts normally.
This matters because many container images default to root. Setting runAsNonRoot: true without also setting runAsUser creates a dependency on the image itself being well-configured. If the image defaults to a non-root user, the Pod starts. If it defaults to root, the Pod fails. You get explicit feedback instead of a silent security gap.
nano nonroot-pod.yamlapiVersion: v1kind: Podmetadata: name: nonroot-podspec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: app image: nginx:1.28kubectl apply -f nonroot-pod.yamlrunAsNonRoot: true alone does not change which user the container runs as. It only blocks startup if the effective user is root. To actually run as a specific non-root UID, combine it with runAsUser. If you set runAsNonRoot: true without runAsUser on an image that defaults to root, the Pod will fail to start with a message indicating that the container cannot run as root.
runAsUser: pinning the UID
runAsUser specifies the exact UID the container process runs as, overriding whatever the container image declares as its default user.
kubectl get pod nonroot-podCheck that the Pod reached Running status. Then inspect the security context that was applied:
kubectl describe pod nonroot-podUnder the Containers section, in the Security Context block, you will see Run As User: 1000 and Run As Non Root: true. These are the values Kubernetes enforced at container start.
A Pod spec sets only runAsNonRoot: true with no runAsUser. The container image has no USER instruction in its Dockerfile and defaults to root. What happens when you apply this Pod?
Reveal answer
The Pod fails to start. Kubernetes checks the effective UID before allowing the container to run. Since the image defaults to UID 0 and no runAsUser override is set, the check fails and the container is rejected with a security violation message. The Pod status will show an error rather than Running.
runAsGroup and fsGroup: controlling group identity
runAsGroup sets the primary GID for the container process. Without it, the process inherits the default group from the container image. Setting it explicitly gives you the same kind of predictability that runAsUser gives for UIDs.
fsGroup is a pod-level field with a specific purpose: it controls the GID applied to volumes mounted into the Pod. When a volume is mounted, Kubernetes changes the ownership of that volume’s files to fsGroup. Any file created inside that volume inherits this GID. This is critical when multiple containers in the same Pod need to share a volume and both need write access under the same group.
Add both fields to the manifest:
nano nonroot-pod.yamlapiVersion: v1kind: Podmetadata: name: nonroot-podspec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 2000 fsGroup: 3000 containers: - name: app image: nginx:1.28kubectl apply -f nonroot-pod.yamlkubectl describe pod nonroot-podThe describe output will now show Run As User: 1000, Run As Group: 2000, and FS Group: 3000 in the security context block.
fsGroup only affects mounted volumes, not the container's root filesystem. If your container does not mount any volumes, fsGroup has no visible effect on the running process.
The failure case: image defaults to root
It is worth watching this fail once so the error message becomes recognizable. Create a Pod with runAsNonRoot: true but without a runAsUser override, targeting an image that defaults to root:
nano rootfail-pod.yaml# illustrative onlyapiVersion: v1kind: Podmetadata: name: rootfail-podspec: securityContext: runAsNonRoot: true containers: - name: app image: nginx:1.28kubectl apply -f rootfail-pod.yamlkubectl get pod rootfail-podThe Pod will not reach Running. Check the details:
kubectl describe pod rootfail-podIn the Events section you will see a message stating that the container cannot run as root. The image nginx:1.28 runs as root by default, and runAsNonRoot: true blocked it. To fix this, add runAsUser: 1000 to the spec. The security gate did exactly what it was supposed to do.
You want to guarantee that a Pod’s containers always run as UID 1000 and are blocked if something tries to start them as root. Which two fields do you combine, and at which spec level?
Try it: Apply a Pod with runAsUser: 1000 and runAsNonRoot: true in spec.securityContext, then run kubectl describe pod <your-pod-name> and read the Security Context block.
Reveal answer
In the Containers section under Security Context, you will see Run As User: 1000 and Run As Non Root: true. Both fields appear together because they serve different roles: runAsUser sets the identity, runAsNonRoot enforces it as a validation gate.
You now know how to control the user identity a container process runs under. The next lesson moves to capability-level hardening and filesystem write restrictions: two container-level fields that constrain what a process can do even after it has started.