Safeguard
Containers

Kubernetes runAsUser: How to Run Containers as a Non-Root User

What the Kubernetes runAsUser security context does, how to set it correctly, and the common mistakes that quietly leave pods running as root.

Karan Patel
Platform Engineer
5 min read

The Kubernetes runAsUser field sets the numeric UID that a container's processes run as, and setting it to a non-zero value is the primary way to stop your pods from running as root. By default many container images run as UID 0, which means a container breakout hands an attacker root-equivalent access on the node. Configuring runAsUser in the security context, together with a couple of companion fields, is one of the highest-leverage hardening steps available in a Kubernetes manifest.

This guide covers what the field does, where to set it, the fields you must set alongside it, and how to enforce non-root across a cluster.

What runAsUser Actually Controls

runAsUser takes an integer UID. When set, every process in the container starts with that user ID regardless of the USER directive baked into the image. You can set it at two levels:

  • Pod level, under spec.securityContext, where it becomes the default for every container in the pod.
  • Container level, under spec.containers[].securityContext, where it overrides the pod-level value for that specific container.

The runAsUser Kubernetes setting is often the first thing reviewers look for in a manifest, because a missing or zero value is a direct signal that a workload may be running with far more privilege than it needs.

Here is a minimal pod-level example that runs as UID 1000:

apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    runAsNonRoot: true
    fsGroup: 2000
  containers:
    - name: app
      image: registry.example.com/web:1.4.2
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]

Why runAsUser Alone Is Not Enough

Setting runAsUser: 1000 tells the kubelet to launch the process as UID 1000. But there is a subtle gap: if you set a non-zero UID and the image still expects root, the container may fail to start, and if you get the value wrong nothing forces the point. That is what runAsNonRoot: true is for. It is a guardrail that makes the kubelet refuse to start the container if it would run as UID 0, whether because runAsUser was omitted or set to zero.

Use them together. runAsUser picks the identity; runAsNonRoot enforces that the identity is not root. Without the enforcement flag, a manifest change or a rebuilt image can silently reintroduce root.

The companion fields matter just as much:

  • allowPrivilegeEscalation: false prevents a process from gaining more privileges than its parent (blocking setuid-based escalation).
  • capabilities.drop: ["ALL"] strips Linux capabilities the container almost never needs.
  • readOnlyRootFilesystem: true stops a compromised process from writing to the image filesystem.
  • fsGroup sets group ownership on mounted volumes so a non-root user can actually read and write them.

The Filesystem Permission Trap

The most common reason teams revert to root is volume permissions. When you switch to a non-root UID, mounted volumes and files baked into the image may be owned by root and unreadable by your new user. Two fixes:

  1. Set fsGroup so Kubernetes recursively adjusts group ownership on volumes to a group your user belongs to.
  2. Build the image so application directories are owned by the non-root UID at build time (chown in the Dockerfile before switching USER).

Trying to solve this by reverting to root defeats the entire exercise. Fix the ownership, not the identity.

Enforcing Non-Root Across a Cluster

Setting runAsUser per manifest relies on every author remembering. For real assurance, enforce it at the cluster level with Pod Security Admission. Applying the restricted Pod Security Standard to a namespace rejects pods that run as root or omit the required security context fields:

apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest

For more granular policy, a policy engine such as Kyverno or OPA Gatekeeper can require a non-zero runAsUser, forbid privileged containers, and validate the full security context on admission. This is what turns "we set runAsUser in this manifest" into "no root workload can ever land in this namespace."

How This Fits Supply Chain Security

Running as non-root limits the blast radius if a container is compromised, but it does not address why a container might be compromised in the first place. A vulnerable dependency inside the image is still exploitable whether the process runs as UID 0 or UID 1000; non-root simply contains what an attacker can do next. Scanning your images for known-vulnerable packages with software composition analysis and enforcing non-root with runAsUser are complementary controls, and mature teams do both.

Defense in depth is the point. runAsUser reduces privilege; dependency scanning reduces the chance of a break-in.

FAQ

What is the difference between runAsUser and runAsNonRoot?

runAsUser sets the specific numeric UID the container runs as. runAsNonRoot is a boolean guardrail that makes the kubelet refuse to start any container that would run as UID 0. Set runAsUser to a non-zero value and runAsNonRoot: true together.

What UID should I use for runAsUser?

Any non-zero UID works; 1000 is a common convention. The important thing is that it is not 0 (root) and that your image's files and mounted volumes are readable and writable by that UID, which you manage with fsGroup and image build-time ownership.

Why does my container crash when I set runAsUser?

Usually a filesystem permission problem: files in the image or on mounted volumes are owned by root and unreadable by the new user. Fix it with fsGroup for volumes and by chown-ing application directories to the non-root UID in your Dockerfile.

How do I enforce non-root across all pods?

Apply the restricted Pod Security Standard to your namespaces via Pod Security Admission, or use a policy engine like Kyverno or OPA Gatekeeper to require a non-zero runAsUser and reject privileged containers at admission time.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.