info@kube-it-consulting.com
BelgiumFranceSwitzerlandUnited Arab Emirates
LinkedInFacebook
Kube IT Consulting
My coursesContact us

Security Context Constraints — why your Helm chart won't start

How OpenShift SCCs assign arbitrary UIDs, why that breaks images built for root, how to read the admission error, and how to fix it without granting anyuid.

What happens when you create a pod
  1. Request arrivesYour PodSpec asks for a UID, capabilities and volumes.
  2. SCCs are matchedAdmission finds every SCC the requesting service account may use.
  3. One is chosenThe least-privileged SCC that can admit the pod wins.
  4. Fields are mutatedMissing values are filled from the SCC — often an arbitrary UID.
  5. Pod runsNo SCC can admit it, and creation is rejected outright.

A chart installs on your laptop’s kind cluster, on staging in EKS, and on any GKE cluster you point it at. On OpenShift the pod never starts, or it starts and immediately fails writing to a directory it created itself.

The cause is almost always Security Context Constraints, and the fix is almost never the one people reach for first.

Your container does not get the UID in its Dockerfile

This is the sentence that resolves most of the confusion.

OpenShift assigns each project a UID range, something like 1000750000/10000, and the default restricted-v2 SCC runs your container as the first UID in that range. USER 1001 in your Dockerfile is a request, not a guarantee. USER root is a request that gets denied.

The container therefore runs as a UID that exists in no /etc/passwd, owns nothing on the filesystem, and belongs to group 0 as a supplementary group.

An image is compatible with this when:

  • It writes only to paths that are group-writable by GID 0, or to mounted volumes
  • It does not need to chown anything at runtime
  • It does not look itself up in /etc/passwd and fall over when absent

Plenty of upstream images satisfy that. Plenty of popular ones do not.

Reading the error you actually get

Three failures, three different messages, one root cause.

Rejected at admission. The pod never appears; the ReplicaSet events carry the reason:

Error creating: pods "web-7c9f4d8b6-" is forbidden:
unable to validate against any security context constraint:
[provider "restricted-v2": .spec.securityContext.runAsUser:
Invalid value: 0: must be in the ranges: [1000750000, 1000759999]]

That is the clearest one. The chart asked for root and no SCC available to that service account permits it.

Starts, then crashes on a write. Admission passed, the process runs as an unexpected UID, and the first write to a COPY-ed directory fails with EACCES. Look for Permission denied in the container log with a path that is not a volume.

Starts, then crashes looking itself up. Some runtimes call getpwuid() at startup. With no matching passwd entry they exit with something unhelpful about the current user being unknown.

To see which SCC a running pod got:

oc get pod <name> -o jsonpath='{.metadata.annotations.openshift\.io/scc}{"\n"}'

And to see what the service account may use:

oc adm policy who-can use scc restricted-v2
oc describe scc restricted-v2

The fix people reach for, and why to resist it

The first search result tells you to do this:

oc adm policy add-scc-to-user anyuid -z my-service-account

It works immediately. It also grants that workload permission to run as root, which is the exact control OpenShift added and the reason a security team signed off on the platform. Doing it once for a vendor image you cannot rebuild is a defensible exception. Doing it as the standard remedy means you are running a more permissive cluster than the one you procured, and nobody will notice until an audit.

If you do grant it, grant it to a dedicated service account for that one workload, never to default, and write down why.

Fixing the image instead

Most breakage disappears with two changes to the Dockerfile.

Make the writable paths group-writable. Group 0 is the supplementary group every OpenShift container gets:

RUN mkdir -p /app/data \
 && chgrp -R 0 /app \
 && chmod -R g=u /app
USER 1001

chmod g=u copies the user bits to the group bits, so whatever UID is assigned can write through group 0.

Do not depend on a passwd entry. If the runtime insists, add an nss_wrapper shim or seed a passwd file at startup. Many base images from Red Hat’s UBI catalogue already handle this.

Fixing the chart instead

Sometimes the image is fine and the chart is asserting a securityContext it doesn’t need. Look for these and remove them rather than overriding the platform:

securityContext:
  runAsUser: 1001      # delete — let the SCC assign it
  fsGroup: 1001        # usually delete — the SCC sets this too
  runAsNonRoot: true   # keep, it is already true

Charts that expose podSecurityContext: {} as a value make this a one-line override. Charts that hardcode it need a patch, and increasingly ship an openshift: true flag that does this for you — check the values file before you fork anything.

When you genuinely need a custom SCC

Real cases exist: a workload that needs NET_ADMIN, a CSI driver that needs host paths, a legacy application that cannot be rebuilt and must hold a fixed UID.

Write a custom SCC that grants only what is needed rather than reaching for privileged:

kind: SecurityContextConstraints
apiVersion: security.openshift.io/v1
metadata:
  name: legacy-fixed-uid
runAsUser:
  type: MustRunAs
  uid: 1001
seLinuxContext:
  type: MustRunAs
allowPrivilegedContainer: false
allowPrivilegeEscalation: false
requiredDropCapabilities: ['ALL']
volumes: ['configMap', 'secret', 'persistentVolumeClaim', 'projected', 'emptyDir']
users: []

Bind it to one service account, and review it when the workload changes. An SCC is a security control that outlives the incident that motivated it.

The rule that keeps this from recurring

Test on OpenShift before you promise a delivery date. A chart that has only ever run on a cluster with no admission policy tells you nothing about how it behaves under one.

If you are building the muscle for this, SCC behaviour sits inside the EX280 objectives under application security, and it is one of the objectives candidates most often meet for the first time in the exam.

Next steps

Practise it

Run the DO280 track in a real terminal

Every objective on CertLabs is graded against live system state rather than the command you typed, on a sandboxed cluster that resets between exercises. The DO280 track covers Deployments, scaling, GitOps.

Open CertLabs

CertLabs is our own practice platform.

Get help

Running this in production?

We operate Kubernetes and OpenShift for clients across the EU and the Gulf, and train the teams who inherit them. Platform assessments, migrations and hands-on enablement.

Talk to us