OpenShift is a per-core subscription across your worker nodes, and it bundles a lot of opinionated platform on top of Kubernetes. Moving to upstream Kubernetes removes the license, but the real work is replacing everything OpenShift quietly provided, the integrated registry, build system, ingress model, and security defaults, with CNCF equivalents you now assemble and own. Go in knowing exactly what you’re rebuilding.
What OpenShift adds over vanilla Kubernetes
OpenShift is not just Kubernetes with a console. It ships an integrated image registry, source-to-image (S2I) builds via BuildConfig, the Route ingress object, DeploymentConfig, SecurityContextConstraints (SCC) for pod security, an OAuth/identity layer, an opinionated web console, and Operator Lifecycle Manager. Each of those is something you must replace or re-implement.
What the subscription actually buys
It helps to name what the subscription buys, because you are about to rebuild it. OpenShift ships an integrated platform where the registry, build system, ingress, identity, and security policy are wired together and supported as one product. A developer pushes source, an ImageStream trigger fires a BuildConfig, S2I produces an image, and a DeploymentConfig rolls it out, all inside the cluster with one console and one support contract. Upgrades arrive as a tested bundle rather than a set of independently versioned components you reconcile yourself. On upstream Kubernetes each of those becomes a discrete choice: which ingress controller, which registry, which CI system, which identity provider, which policy engine, and who owns keeping them compatible across upgrades. None of it is hard in isolation. The cost is that you now own the integration and the on-call for it. Scope the platform-team time honestly before you sign off on the license savings, because that trade is the whole decision.
What maps to what
- Routes → Ingress or the Gateway API (with an ingress controller such as NGINX or Envoy-based options).
- BuildConfig + ImageStream + S2I → external CI (Tekton, Argo Workflows, GitHub Actions) plus a registry like Harbor.
- DeploymentConfig → standard Deployment (triggers and rollout hooks become CI/GitOps steps).
- SCC → Pod Security Admission (PSA), optionally with a policy engine (Kyverno or OPA Gatekeeper) for finer control.
- Internal registry → Harbor (or Quay, which is also available standalone).
- OpenShift OAuth → external OIDC (Keycloak, Dex) wired into the API server.
oc→kubectl; the web console → Headlamp, Lens, or the Kubernetes Dashboard.- Operators mostly port, OLM is available upstream, and many operators publish community builds.
Choosing a distribution
“Upstream Kubernetes” still needs a lifecycle owner. Common choices: kubeadm or Cluster API for roll-your-own; RKE2/Rancher, k3s, or Talos Linux for a managed-feel open distribution. This decision drives upgrades, HA, and node provisioning, so make it before you migrate workloads.
The security-model shift to plan for
The conversion that surprises teams is SCC to Pod Security Admission. OpenShift’s SCCs are stricter and shaped differently from upstream PSA levels (privileged / baseline / restricted). Workloads that “just ran” under a permissive SCC may need explicit securityContext settings, and anything relying on OpenShift’s default non-root UID ranges needs review. Audit this early, it’s a correctness and security issue, not a cosmetic one.
Converting the manifests
The containers themselves rarely change, but the objects wrapping them do. Convert each DeploymentConfig to a standard Deployment and move its triggers and lifecycle hooks into CI or GitOps steps. Replace ImageStream references with direct image pulls from your new registry, updating every manifest that pointed at the internal OpenShift registry. Turn Routes into Ingress rules, and re-check any Route-level annotations that have no clean Ingress equivalent. Where an app depended on OpenShift’s automatic non-root UID assignment, add an explicit securityContext so the pod still runs under Pod Security Admission. Do this conversion in version control so the target is declarative and reviewable, not hand-edited on a live cluster.
A safe migration flow
- Inventory Routes, BuildConfigs, ImageStreams, DeploymentConfigs, SCCs, and Operators in use.
- Stand up the target cluster on your chosen distribution, with ingress, a registry (Harbor), CI, and an OIDC provider.
- Convert manifests, replace OpenShift-specific objects with standard Kubernetes YAML, and move build logic into external pipelines.
- Migrate workloads with Velero (backup/restore of namespaces and persistent volumes) and redeploy via GitOps (Argo CD or Flux).
- Validate per namespace: pods schedule under PSA, ingress resolves, images pull from the new registry, CI rebuilds, and auth works.
- Cut over namespace by namespace, keeping the OpenShift cluster available for rollback through hypercare.
Validation and cutover
Validation is per namespace and should be explicit, not a glance at a green dashboard. Confirm that pods schedule and stay running under the PSA level you assigned, that Ingress resolves the same hostnames with working TLS, and that images pull from the new registry rather than the retired internal one. Run the CI pipeline end to end so a source change rebuilds and redeploys, and check that external OIDC login grants the roles your rebuilt RBAC expects. Reconcile the whole namespace through GitOps so what is running matches what is committed. Only then move traffic, and keep the OpenShift namespace intact through a hypercare window so you can re-point back if a wave regresses.
What to do next
OpenShift → upstream Kubernetes removes the per-core subscription but converts a turnkey platform into an assembly-and-own model. The heavy lifting is re-tooling builds and ingress and migrating SCC-based security to Pod Security Admission, not moving the containers themselves. Choose your distribution first, rebuild the platform services, then migrate workloads with Velero and GitOps. Model the per-core savings in the calculator above against the engineering time you’re taking on, and treat the figures as provisional until you scope your own platform team.