# Container Running As Root

> A container without runAsNonRoot runs as UID 0 inside the container by default. That is not root on the node, but it removes a meaningful barrier: a container escape starts from root rather than an unprivileged user. ZopNight checks 3 workload kinds.

Source: https://zop.dev/integrations/kubernetes/recommendations/container-running-as-root
Updated: 2026-08-19

---

## Root in a container is not root on the node

Worth stating precisely, because this finding is often over- and under-stated.

UID 0 inside a container is namespaced. Without privileged mode or added capabilities, that
process still cannot touch the host directly. The container boundary holds.

What it changes is what happens **after** a boundary failure. A container-escape vulnerability
exploited from UID 0 lands you as root. The same escape from UID 1000 lands you as an
unprivileged user who then needs a second privilege-escalation step.

It is defence in depth rather than a boundary in itself, which is why this is worth fixing
broadly and rarely worth an emergency.

## Why so many images default to root

Because building them that way is easier. Installing packages needs root, binding to ports below
1024 needs root or `NET_BIND_SERVICE`, and writing to paths created during build needs matching
ownership.

Many official images still run as root unless you override it, so a workload can be running as
UID 0 without anyone having chosen that.

## The three settings, and the one that actually enforces

```yaml
securityContext:
  runAsNonRoot: true      # admission REJECTS the pod if the image would run as UID 0
  runAsUser: 1000         # sets the UID, but an image can be built to ignore intent
  allowPrivilegeEscalation: false
```

`runAsNonRoot: true` is the enforcing one, failing closed at admission rather than trusting the
manifest. Setting `runAsUser` alone is a request, not a guarantee.

## What breaks when you switch

Two things, predictably: a process binding to port 80 or 443, and a container writing to a path
owned by root. Fix the first by binding above 1024 and letting the Service map the port; fix the
second with `fsGroup` or by correcting ownership in the image.

## Testing runAsNonRoot on the pod securityContext

```bash
kubectl get deploy,statefulset,daemonset -A -o json | jq -r '
  .items[] | . as $w | .spec.template.spec
  | select((.securityContext.runAsNonRoot // false) == false)
  | "\($w.kind) \($w.metadata.namespace)/\($w.metadata.name)"'
```

## Enforcing it

Pod Security Admission at `restricted` requires `runAsNonRoot`. Apply it to new namespaces first.
Retrofitting it to a namespace full of root-running workloads blocks deploys until every image
is fixed.
