Kubernetes "container has runAsNonRoot and image will run as root": fix the image or the pod
CreateContainerConfigError with runAsNonRoot: the image runs as root or has a non-numeric USER. Fix the Dockerfile, set runAsUser, and keep restricted security.
by OhSudo ··
Not sure this is your cause?Find it step by step: Pod not running →
Part of a learning path:Kubernetes: debugging pods
The pod never starts. kubectl get pods shows a status you don’t see often:
NAME READY STATUS RESTARTS AGEweb-7c9d8b6f5d-x2k4q 0/1 CreateContainerConfigError 0 41sand kubectl describe pod explains it in the Events at the bottom:
Warning Failed 12s (x4 over 41s) kubelet Error: container has runAsNonRoot and image will run as root (pod: "web-7c9d8b6f5d-x2k4q_shop(3f1c…)", container: web)Your pod spec says runAsNonRoot: true — a good security setting — and the image says it runs as root. The kubelet checks the two before it starts the container, sees the conflict, and refuses. Nothing is wrong with the cluster; the image and the manifest disagree.
- Pod specrunAsNonRoot: trueand
- Imageruns as root (or a user name, not a number)conflict(fails)
- kubeletCreateContainerConfigError(breaks here)
TL;DR
kubectl describe pod <pod> | tail -n 5 # the exact errordocker image inspect -f '{{.Config.User}}' <image> # empty or "root" = runs as rootkubectl get pod <pod> -o jsonpath='{.spec.securityContext}' # where runAsNonRoot comes fromThen either give the image a numeric non-root user (USER 10001 in the Dockerfile), or set runAsUser: 10001 in the pod’s securityContext.
1. Check which user the image runs as
The user comes from the last USER line of the Dockerfile — or from the base image, if the Dockerfile has none:
docker image inspect -f '{{.Config.User}}' registry.example.com/web:1.4.2- Empty,
rootor0: the image runs as root. Most official base images (node,python,nginx,golang) do, unless you change it. - A name, like
nodeorapp: see section 3 — this is the other variant of the error. - A number, like
10001or1000:1000: fine; the error must come from something else (arunAsUser: 0in the pod spec).
Without Docker at hand, crane config <image> | jq .config.User or skopeo inspect --config docker://<image> show the same.
2. Fix the image: a numeric non-root user
The cleanest fix is in the Dockerfile — create a user and switch to it, by number:
FROM python:3.13-slimRUN useradd --system --uid 10001 --no-create-home --shell /usr/sbin/nologin appWORKDIR /appCOPY --from=build /opt/venv /opt/venvCOPY . .USER 10001CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]Official images often already have a user you can reuse: node images have node (UID 1000), nginxinc/nginx-unprivileged runs as 101, distroless images have nonroot (65532). The Dockerfile Generator writes multi-stage Dockerfiles that end with a numeric USER for exactly this reason.
Note
A non-root process can’t bind ports below 1024. If your app listens on 80, move it to 8080 (and keep port 80 on the Service: port: 80, targetPort: 8080). Files it writes need to be writable by that user — a mounted volume, or a folder you chown in the Dockerfile.
3. “image has non-numeric user (node), cannot verify user is non-root”
Error: container has runAsNonRoot and image has non-numeric user (node), cannot verify user is non-root (pod: "web-…", container: web)The image does run as a normal user — but by name. The kubelet only sees the image config, not the image’s /etc/passwd, so it can’t know whether node is UID 1000 or UID 0. With runAsNonRoot: true it refuses rather than guess. Two fixes, either is enough:
- In the Dockerfile, use the number:
USER 1000instead ofUSER node. - In the pod spec, tell Kubernetes the UID, so it doesn’t need to look it up:
spec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 10004. Can’t change the image? Set runAsUser
For a third-party image that runs as root but doesn’t need to, set the user in the pod. Kubernetes then starts the process with that UID, whatever the image says:
spec: securityContext: runAsNonRoot: true runAsUser: 10001 runAsGroup: 10001 fsGroup: 10001 # mounted volumes become writable for this group containers: - name: web image: registry.example.com/web:1.4.2 securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"]The pod now starts — but the app may fail next, with Permission denied on a file the image created for root, or bind: permission denied on port 80. Then the image really needs root for something; fix the image (section 2) rather than dropping runAsNonRoot. The Kubernetes Manifest Generator writes this whole security context, which meets the “restricted” Pod Security Standard.
5. Where runAsNonRoot comes from
If you don’t remember setting it, look for it:
kubectl get pod web-7c9d8b6f5d-x2k4q -o jsonpath='{.spec.securityContext}{"\n"}{.spec.containers[*].securityContext}{"\n"}'It can come from your manifest, from a Helm chart’s default values (many charts set podSecurityContext.runAsNonRoot: true), or from a policy engine like Kyverno or Gatekeeper that adds it automatically. Removing it makes the error go away — and runs your app as root again. Prefer fixing the image.
Shell, we have a problem!
Similar, but different: pods "web-…" is forbidden: violates PodSecurity "restricted:latest": runAsNonRoot != true appears when the pod is created, in the events of the ReplicaSet (kubectl describe rs), not of the pod. That’s the namespace’s Pod Security Admission rejecting a pod that doesn’t set runAsNonRoot: true — the opposite problem: add the security context from section 4. And on OpenShift, pods run with a random UID by default, so images must work as any user, not just one specific UID.
After the fix
kubectl rollout restart deployment/webkubectl rollout status deployment/webkubectl exec deploy/web -- id # uid=10001 … — not uid=0(root)$ ./practice.sh
Practice this in the terminal
Open the Terminal Playground with this article’s scenario loaded. It is safe and runs in your browser.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.