OpenShift is Kubernetes — same API, same pods, same kubectl mostly works. And yet, workloads that run perfectly on vanilla Kubernetes or managed services like EKS/GKE routinely fail on OpenShift, and the error messages rarely point at the real cause. I move workloads between both worlds in telecom environments, and the failure points are always the same short list.

This is the migration guide I'd give any team before their first deployment to OpenShift.

The big difference: OpenShift doesn't trust your container

Vanilla Kubernetes runs containers as whatever user the image declares — usually root. OpenShift runs them with a random, high-numbered UID enforced by Security Context Constraints (SCCs). This is the root of almost every "works everywhere but OpenShift" failure.

Your app tries to write to a directory owned by root, or bind to a privileged port, or execute a file it doesn't own — and dies with a permission error that looks like a bug in your code.

The fix, in your Dockerfile

Make every writable path owned by the root group (GID 0) and group-writable:

RUN chgrp -R 0 /app && chmod -R g=u /app 

g=u gives the group the same permissions as the user. OpenShift runs the container with an arbitrary UID that is always a member of the root group (GID 0), so your app can now write wherever the image user could. Never use chown 1000:1000 — the runtime UID is random and you can't predict it.

Also: set a numeric non-root USER in your Dockerfile (e.g. USER 1001). OpenShift will still override it with its own UID, but it proves the image doesn't need root. And don't rely on writing outside your app directory.

Routes instead of Ingress resources

On vanilla Kubernetes you expose services with an Ingress resource plus an ingress controller. OpenShift uses Routes, built on its HAProxy router:

oc expose service my-service --hostname myapp.example.com 

or in YAML:

apiVersion: route.openshift.io/v1 kind: Route metadata: name: my-app spec: to: kind: Service name: my-service tls: termination: edge 

Good news: An edge Route without its own certificate uses the router's default wildcard certificate — one less cert-manager headache for internal apps. If you must keep standard Ingress manifests, OpenShift supports them, but Routes are the native path — and the one your cluster admin expects.

API objects that don't exist on vanilla Kubernetes

OpenShift-specific resources you'll meet immediately:

  • Routes (shown above)
  • Projects — namespaces with annotations and quotas; oc new-project is the normal entry point
  • DeploymentConfigs — the legacy OpenShift deployment object, deprecated since OpenShift 4.14. Prefer standard Deployments unless you need triggers; DCs exist mostly for backward compatibility
  • ImageStreams — a layer over container registries that tracks tags and enables rollbacks. You can ignore them and reference images directly, but builds (oc start-build) produce ImageStreams

The kubectl/oc reality

kubectl works against OpenShift for standard resources. You'll want oc for the rest:

oc login https://api.mycluster.example.com:6443 oc project my-namespace oc get routes oc adm policy who-can delete pods -n my-namespace 

The oc adm policy subcommands matter more than people expect: the default project template often grants developers edit on their own project but nothing on others, and SCC permissions (oc adm policy add-scc-to-user) are how you handle the rare workload that genuinely needs elevated privileges.

The pre-flight checklist

Before migrating a workload, run this against your manifests:

  1. Does the image run as non-root with random UID? Fix with chgrp 0 + g=u on every writable path.
  2. Any hostPath, privileged, or runAsUser: 0? These require a custom SCC. Ask whether they're truly needed — most of the time they're legacy assumptions.
  3. Any privileged ports (<1024)? Move to a higher port and let the Route/Service handle exposure.
  4. Ingress manifests? Plan the Route equivalents.
  5. Helm charts? Look for hardcoded runAsUser or fsGroup values — they often fall outside the project's allowed UID range and get rejected.
  6. Storage classes and CSI? Standard, but confirm the cluster's default StorageClass — OpenShift clusters often ship with their own.
  7. Any Docker socket or host access? Will be rejected by the restricted SCC without an explicit exception.

Summary

OpenShift breaks vanilla Kubernetes workloads for one main reason: it runs containers with an arbitrary UID under Security Context Constraints. Fix your Dockerfiles (chgrp -R 0, chmod -R g=u, no root), replace Ingress with Routes, learn the handful of OpenShift-specific objects, and run the pre-flight checklist before the first deploy. The migration itself is much faster — when you know the list of things that break.

One last piece of advice: rehearse the migration first with a dry run in a test cluster or namespace. Don't go straight to production. Test first, and please, never on a Friday.