Someone says “we run OpenShift” and a Kubernetes learner hears “we run something else entirely.” That is rarely true. OpenShift is a distribution of Kubernetes with Red Hat packaging, opinionated defaults, and extra platform APIs. The scheduler still schedules Pods. Deployments still roll out ReplicaSets. Services still select Pods by labels.
I learned this the hard way. I spent months getting comfortable with kubectl, Ingress, and namespace RBAC on vanilla clusters. Then I joined a team on OpenShift and felt like a beginner again — not because Pods worked differently, but because the front door for HTTP traffic was a Route, the security model included SCCs, and oc had shortcuts I did not recognize.
This post is for that transition. I will not pretend OpenShift is trivial or that it is identical to every managed Kubernetes offering. I will separate what transfers cleanly from what deserves a deliberate read of the docs.
Same Kubernetes core underneath
OpenShift ships a tested, supported Kubernetes version. The API groups you use daily — apps/v1 Deployments, v1 Services and ConfigMaps, batch/v1 Jobs — are upstream Kubernetes resources.
If you can write this Deployment, it runs on OpenShift the same way:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
labels:
app: api
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.example.com/team/api:1.4.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Apply it with kubectl or oc — both talk to the same API server:
kubectl apply -f deployment.yaml
# or
oc apply -f deployment.yaml
Check it the way you already would:
kubectl get deploy,rs,pod -l app=api
kubectl describe pod -l app=api
kubectl logs -l app=api --tail=50
On OpenShift, the same commands work. oc adds conveniences — oc status, oc logs -f deployment/api, project switching — but it is not a separate orchestrator. Think of it as kubectl plus platform-aware helpers.
What stays familiar:
- Pod lifecycle, probes, and restart policies
- Deployments, StatefulSets, DaemonSets, CronJobs
- Services, Endpoints, EndpointSlices, DNS inside the cluster
- ConfigMaps, Secrets, PersistentVolumeClaims
- NetworkPolicy (with OVN-Kubernetes as the default CNI on modern OpenShift)
- RBAC with Roles, RoleBindings, ClusterRoles
When documentation says “Kubernetes does X,” OpenShift usually does X too — unless the platform adds a wrapper or a stricter default.
Vendor packaging — what Red Hat adds
Calling OpenShift “just Kubernetes” undersells the platform layer. Calling it “completely different” oversells it. The honest middle ground: Red Hat ships Kubernetes plus an integrated product stack.
That stack typically includes:
- OpenShift Router — exposes HTTP/S via Route objects (OpenShift’s long-standing edge pattern)
- Ingress Controller — also available; Ingress resources work, often alongside Routes
- Built-in image registry —
image-registry.openshift-image-registry.svcinside the cluster - Operators and OLM — install and lifecycle-manage platform and third-party software
- Web console — GUI for projects, routes, builds, monitoring
- Monitoring and logging — Prometheus, Alertmanager, cluster logging operators pre-integrated on many installs
- Security Context Constraints — Pod admission rules beyond Pod Security Standards
- Projects — namespaces with extra metadata and default quotas/limits
You may not touch all of these on day one. Application developers often interact with Projects, Routes, Deployments, Services, and Secrets. Platform teams live in Operators, SCCs, cluster networking, and upgrade channels.
The packaging matters for operations, not for the mental model of “I deploy a containerized app behind a Service.” That model still holds.
Routes — the HTTP front door you will meet first
On many vanilla clusters, HTTP ingress means an Ingress resource plus a separately installed Ingress controller (nginx, traefik, AWS ALB, etc.). OpenShift has had Route as a first-class route.openshift.io API for years.
A Route maps an external hostname and path to a Service:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: api
labels:
app: api
spec:
host: api.apps.cluster.example.com
to:
kind: Service
name: api
weight: 100
port:
targetPort: 8080
tls:
termination: edge
insecureEdgeTerminationPolicy: Redirect
The OpenShift Router (historically HAProxy-based; implementations evolve) watches Route objects and programs edge load balancing. Edge TLS termination is common — the Router terminates HTTPS and forwards HTTP to the Service inside the cluster.
If you know Ingress, Routes will feel familiar with different field names:
| Concept | Ingress (typical) | OpenShift Route |
|---|---|---|
| External hostname | spec.rules[].host | spec.host |
| Backend Service | backend.service | spec.to.name |
| Port | port.number | spec.port.targetPort |
| TLS | spec.tls | spec.tls with termination modes |
OpenShift also supports standard Ingress resources. Teams sometimes standardize on Ingress for portability and use Routes where the platform defaults are simpler. Both can coexist. I have seen teams pick one pattern per environment to avoid confusion.
For kubectl users:
oc get route
oc describe route api
kubectl get route -n my-project
Routes are the single biggest “this feels different” moment for Kubernetes users. They are not a reason to relearn Deployments — they are a reason to learn one new object at the edge.
Security Context Constraints — Pod security with OpenShift flavor
Kubernetes now has Pod Security Admission with restricted, baseline, and privileged levels. OpenShift adds Security Context Constraints (SCCs) — an older, richer admission layer that still governs many clusters.
An SCC answers questions like:
- May this Pod run as root (UID 0)?
- Which volume types are allowed?
- May it use hostNetwork or hostPID?
- Which SELinux contexts are permitted?
By default, most user workloads land on the restricted-v2 or similarly named SCC. That means:
- Containers should not assume they can run as root
- Privileged Pods require explicit permission
- Some Helm charts written for permissive clusters fail until security contexts are adjusted
Symptoms look like Kubernetes admission failures:
oc describe pod failing-app-xxxxx
# Events:
# Warning FailedCreate ... unable to validate against any security context constraint
Compare with:
oc get scc
oc describe scc restricted-v2
Fix paths usually involve one of:
- Setting
runAsNonRoot: trueand a concreterunAsUserin the Pod spec - Requesting a less restrictive SCC via RBAC (platform team decision, not something to self-grant)
- Adjusting the chart or image to not require root
I have spent afternoons on SCC errors that looked like mysterious “Pod pending forever” issues. The Deployment was fine. The image wanted root. The platform said no. That is not OpenShift being hostile — it is the platform enforcing a baseline I should have read first.
SCCs overlap conceptually with Pod Security Standards. On newer OpenShift versions, the story is converging, but SCCs remain the object platform admins grep during incidents.
Projects — namespaces with platform semantics
A Project is a Kubernetes Namespace plus OpenShift metadata. For daily work, treat them as the same isolation boundary:
oc project my-team-dev
kubectl config set-context --current --namespace=my-team-dev
oc get all
List projects:
oc get projects
kubectl get namespaces
Differences that matter in practice:
- The web console speaks in “Projects”
- Default ResourceQuotas and LimitRanges are often applied per project by admins
oc new-projectcreates a namespace and sets RBAC for the creator- Some cluster-scoped policies reference project labels
If you understand namespaces, RBAC, and quotas, Projects are not a new concept — they are the name OpenShift uses in UX and docs. I still think in namespaces when writing YAML; the API accepts metadata.namespace either way.
Operators — how platform software is installed
OpenShift embraces the Operator pattern heavily. An Operator is a controller that watches Custom Resources and reconciles complex software — databases, service meshes, the ingress/router stack itself.
Operator Lifecycle Manager (OLM) installs Operators from catalogs. Cluster admins subscribe to an Operator; OLM creates CSVs, Deployments, and CRDs.
As an application developer, you might interact with Operators when:
- A DBA provisions PostgreSQL via a
PostgresClusterCR instead of raw StatefulSet YAML - The monitoring stack is managed by cluster-level Operators you do not touch
- Your team installs a vendor Operator into a namespace for a message queue
As a learner coming from Helm-only clusters, the shift is: some infrastructure is a CRD with a reconciliation loop, not a chart you upgrade manually.
Useful discovery commands:
oc get csv -A
oc get operators
oc get crd | head
kubectl api-resources | grep -i postgres
You do not need to author Operators to work on OpenShift. You do need to recognize when a workload is “just Kubernetes YAML” vs “managed by an Operator CR” — upgrades and backups may be Operator-owned.
When Kubernetes skills transfer cleanly
If you can do these on any Kubernetes cluster, you can do them on OpenShift with minimal friction:
Deploy and roll out applications — Deployments, rollouts, kubectl rollout status, image updates.
Debug Pod failures — kubectl describe pod, logs, events, probe misconfiguration, OOMKilled, image pull errors.
Wire internal traffic — ClusterIP Services, DNS names, port alignment between Service and container.
Manage configuration — ConfigMaps and Secrets, env vars, volume mounts.
Understand RBAC basics — who can create what in which namespace/project.
Read YAML and use dry-run — kubectl apply --dry-run=server -f catches many issues before apply.
GitOps workflows — Argo CD and Flux work on OpenShift; they reconcile the same resource types (plus Routes and other OpenShift CRs if you use them).
NetworkPolicy thinking — label selectors, explicit allow rules, default deny when policies select Pods.
The habits that transfer best are layered debugging and reading events. OpenShift incidents still start with “what changed in the Pod, Service, and route layer?”
Honest limits — what does not transfer automatically
I wish someone had listed these for me earlier.
Ingress muscle memory is incomplete. Routes are the native edge object. Ingress works, but docs, samples, and colleague snippets may assume Routes. Learn both names if your org uses both.
Security assumptions from permissive labs break. Kind and minikube often let you run privileged Pods. OpenShift may not. Charts that “just worked” locally need security context review.
Platform-owned namespaces are not yours. openshift-*, kube-*, and operator namespaces are managed by the cluster. Applying random YAML there is a fast way to lose platform-team trust.
BuildConfigs and ImageStreams are OpenShift-specific. Some teams build outside the cluster (CI pipelines, Tekton, GitHub Actions) and only deploy. Others use oc new-app and BuildConfigs. Know which pattern your team uses before copying old tutorials.
Storage classes and CSI drivers are cluster-specific. PVCs work the same; the storage class names and performance characteristics are not portable.
Documentation sprawl. You will read Kubernetes docs, OpenShift docs, and vendor Operator docs. The answer to “which API version?” depends on the cluster’s Kubernetes minor version — check oc version.
oc vs kubectl policy. Some orgs standardize on oc for audit wrappers or login integration. Some allow kubectl with a kubeconfig from oc login. Ask instead of assuming.
Licensing and support boundaries. OpenShift is a commercial product with subscription tiers. That affects upgrade cadence, support calls, and what the platform team will change for you. Not a technical skill gap, but a workflow difference.
None of these invalidate Kubernetes learning. They are the packaging tax on top of a shared core.
oc and kubectl — practical coexistence
oc embeds kubectl-compatible commands. Many teams alias or use either:
oc login https://api.cluster.example.com:6443
oc whoami
oc project my-team-dev
kubectl get pods
oc get pods
oc apply -f app/
kubectl apply -f app/
OpenShift-specific helpers worth knowing:
oc status
oc get route
oc logs -f deployment/api
oc rsh deployment/api
oc set env deployment/api LOG_LEVEL=debug
oc rollout restart deployment/api
oc explain works on Route and other OpenShift types:
oc explain route.spec.tls
kubectl explain route.spec.tls
I still reach for kubectl out of habit. I use oc when I need login, project context, or Route-aware output. Pick one primary tool per shell session; mixing contexts causes more confusion than mixing commands.
A modest learning path
If you already know Kubernetes basics, I would not restart from zero. I would layer OpenShift in this order:
- Get access —
oc login,oc whoami, list projects, set default project. - Deploy something boring — Deployment + Service, confirm Pods and endpoints.
- Expose it — create a Route, hit the hostname, confirm TLS behavior.
- Break it on purpose — wrong selector, failing readiness probe, non-root violation — and fix each with describe/logs/events.
- Read one SCC and one quota — understand why the platform blocked or throttled something.
- Find what Operators own — identify one CRD your stack depends on and read its status fields.
That path took me a few focused days, not months. The Kubernetes foundation was the part that actually saved time.
OpenShift vs “Kubernetes on cloud” — quick framing
Managed Kubernetes (EKS, GKE, AKS) gives you a control plane and leaves many add-ons as choices. OpenShift ships more decisions pre-made: router, registry integration, console, default monitoring, SCCs.
Neither model is universally better. OpenShift trades flexibility in component choice for integrated operations. Vanilla Kubernetes trades integration for pick-your-own ingress, policy engine, and observability stack.
Compare what your organization actually runs, not marketing slides.
Final thought
OpenShift vs Kubernetes is the wrong binary. Better question: what is upstream Kubernetes, and what is Red Hat platform on top?
Deployments, Services, probes, and RBAC are the shared language. Routes, SCCs, Projects, and Operators are the local dialect. Learn the dialect when you need it — usually at the edge, at admission time, and when talking to platform admins.
I still Google SCC error messages. I still check Endpoints before Routes when HTTP fails. The core skills from kubectl-centric learning were not wasted. They were the part that transferred on day one.