Helm on OpenShift: Migrating to Security Context Constraints
Objective
A Helm chart that works perfectly on vanilla Kubernetes often fails on OpenShift. In this guided lab you’ll deploy a generic nginx Helm chart step by step — from its default values (which fail) to an OpenShift-compatible configuration (which succeeds) — diagnosing each failure along the way with oc/kubectl.
You’ll learn:
- Why OpenShift’s Security Context Constraints (SCC) prevent containers from running as root
- How OpenShift assigns a non-root UID per namespace, and why that breaks images that assume root
- How to adapt a chart (here, the official
nginximage) to run underrestricted-v2
Complete this lab in 20-30 minutes.
Prerequisites
- An OpenShift cluster, logged in with
oc helmv3+kubectl
Setup
Clone the chart and create a dedicated project:
Have a look at the chart’s default values.yaml ($CHART/values.yaml) — it is a typical “vanilla Kubernetes” chart: official nginx image, exposed on port 80 through a LoadBalancer Service:
Exercise 1 — Deploy with the chart’s default values
Install the chart as-is:
Question: Does the install succeed? What’s the status of the pod? Use kubectl get events, kubectl get pods and kubectl logs to find out why.
Note — LoadBalancer quota on the Red Hat Developer Sandbox
On the Red Hat Developer Sandbox, a
ClusterResourceQuotasetsservices.loadbalancers: 0. Requesting aLoadBalancerService is then rejected outright by the admission controller — before any pod is even created (oc get appliedclusterresourcequotashows the quota; no KubernetesEventis generated, the error goes straight back to the Helm client). If your cluster enforces that quota, switch theServicetype toClusterIPfirst (Exercise 2), then come back to the SCC issue above.
Exercise 2 — Switch the Service to ClusterIP
Override the service type only:
(equivalently, use the ready-made values file 6_helm_migration/manifests/nginx-values-v1.yaml)
Question: Does this fix the deployment? Why or why not?
Exercise 3 — Make the chart OpenShift-compatible
Look at 6_helm_migration/manifests/nginx-values-v2.yaml. It changes three things compared to the defaults:
openShiftConfig: true makes the chart mount a custom nginx.conf through a ConfigMap (see $CHART/templates/configmap.yaml) that:
- listens on port
8080(non-privileged) instead of80 - redirects
pidand every*_temp_path(client/proxy/fastcgi/uwsgi/scgi) to/tmp— writable by any UID
Deploy it:
Question: Does it work this time? Which SCC is the running pod actually admitted under — and which UID does it run as?
Going further — inspecting SCCs directly
If you have enough rights, you can look at what restricted-v2 enforces:
You can also ask the API server, before deploying anything, which SCC would admit a given pod spec — this requires create podsecuritypolicyselfsubjectreviews.security.openshift.io, a permission usually unavailable on shared/sandboxed clusters:
Cleanup
Key Takeaways
- SCCs don’t (only) reject pods at admission time —
restricted-v2silently assigns each pod a non-root UID from the range allocated to its namespace (oc get namespace <ns> -o jsonpath='{.metadata.annotations.openshift\.io/sa\.scc\.uid-range}'). The failure then surfaces at runtime, when the application can’t operate under that UID. - The first error you see is rarely the real one. A
LoadBalancerquota error or aServicemisconfiguration can mask a deeper image/platform incompatibility — keep digging (kubectl get events,oc logs,oc describe pod) until the pod is actuallyRunning. - Fixing the chart means adapting the application, not relaxing the platform: use non-privileged ports (
>1024), redirect writable paths to/tmp, and declare asecurityContextthat matches whatrestricted-v2already requires (runAsNonRoot, all capabilities dropped, no privilege escalation,seccompProfile: RuntimeDefault). oc get pod <name> -o jsonpath='{.metadata.annotations.openshift\.io/scc}'tells you exactly which SCC admitted a running pod — the fastest way to confirm what’s really happening.
Reference / full solution
- Full demo script:
ex1-helm-migration.sh - Helm chart used in this lab:
nginx-chart - Values files:
nginx-values-v1.yaml,nginx-values-v2.yaml