Tutorial 11 — Deploy on Red Hat OpenShift (Helm + Operator Path)
> Status note.* The Helm chart at deploy/helm/buttrbase-backend-rust/ is real and committed — **Path A** (Helm-on-OpenShift) is runnable today against any OpenShift 4.10+ cluster. *Path B (packaging the chart as a certified Helm-based Operator and submitting an OLM bundle to OperatorHub) is forward-looking: the operator source does not yet exist in this repo. This tutorial is explicit about which steps you can run now and which describe the target state. Commands in Path A are real; Path B describes the pattern and points at Red Hat's tooling docs rather than inventing CLI flags that have not been tested here.
This tutorial is one layer thin: it explains what OpenShift adds on top of the chart and how to navigate those additions. For everything about how Helm install actually works — topology values, secrets, the /health probe — see 07-deploy-with-helm.md. That document is the substrate; this one references it, it does not repeat it.
1. What OpenShift adds on top of Kubernetes
OpenShift is Kubernetes with three meaningful additions for this chart:
| OpenShift concept | What it means for ButtrBase |
|-------------------|------------------------------|
| SecurityContextConstraints (SCC) | Every pod runs against an SCC. The default restricted SCC disallows runAsRoot and drops most Linux capabilities. The chart does not request root — the backend listens on port 4300 (well above 1024), so no privileged-port exception is needed. Out of the box the pod runs against restricted without modification. |
| Route (not bare Service) | OpenShift's ingress primitive is the Route object, not a LoadBalancer or bare Ingress. The chart's Service is ClusterIP; you expose it externally with oc expose svc/buttrbase, which creates a Route managed by the OpenShift router (HAProxy). |
| OperatorHub / OLM | The platform-native way to install applications is through the Operator Lifecycle Manager. This is Path B — packaging the chart as a Helm-based Operator so it appears in OperatorHub and upgrades are managed by OLM. |
2. Prerequisites
- An OpenShift 4.10+ cluster and the
ocCLI authenticated to it. - Helm 3.12+ installed locally (
helm version). - A reachable Postgres database and its connection URL.
- The three secret values:
database-url,secret-key,encryption-key. Seedocs/secrets-architecture.md.
---
Path A — Install the chart directly on OpenShift (runnable today)
3. Log in and create a project
oc login --server=https://api.YOUR_CLUSTER.example.com:6443 -u YOUR_USER
oc new-project buttrbase
oc new-project is OpenShift's kubectl create namespace equivalent — it creates the namespace and sets it as the active context in one step.
4. Create the Secret
Same three keys the chart reads, same command shape as tutorial 07, using oc instead of kubectl (they are interchangeable for core Kubernetes objects):
oc -n buttrbase create secret generic buttrbase-backend-rust \
--from-literal=database-url='postgres://buttrbase:…@db.internal:5432/buttrbase' \
--from-literal=secret-key="$(openssl rand -hex 32)" \
--from-literal=encryption-key="$(openssl rand -hex 32)"
> The encryption-key encrypts tenant secrets at rest. Losing it means losing access to every encrypted value — store it in your secret manager before you install, not after.
5. SCC note
The default restricted SCC on OpenShift 4.11+ (restricted-v2) requires pods to run as a non-root UID and drops NET_BIND_SERVICE among other capabilities. The chart does not set runAsRoot: true, does not request any capabilities, and the container listens on 4300 — so no SCC customization is needed. The scheduler will assign a random UID from the project's UID range and the container starts cleanly.
If your cluster uses a custom SCC that blocks image pulls from ghcr.io, configure an image pull secret in the buttrbase namespace and add it to the default service account — that is a cluster policy question, not a chart question.
6. Dry-run, then install
Render first to confirm the topology values are what you expect:
helm template buttrbase ./deploy/helm/buttrbase-backend-rust \
-f ./deploy/helm/buttrbase-backend-rust/values-single-tenant-managed.yaml
Confirm BUTTRBASE_RUST_DEPLOYMENT_MODE: "single-tenant-managed" appears in the rendered ConfigMap. Then install:
helm upgrade --install buttrbase ./deploy/helm/buttrbase-backend-rust \
-n buttrbase \
-f ./deploy/helm/buttrbase-backend-rust/values-single-tenant-managed.yaml \
--set app.image.tag=0.1.0
Use values-single-tenant-byoc.yaml if the customer owns the cluster, or values-on-prem.yaml for an air-gapped environment. The topology table in 07-deploy-with-helm.md maps all four files to their use cases.
7. Expose via a Route
The chart's Service is ClusterIP — it is not externally reachable without a Route. Create one:
oc -n buttrbase expose svc/buttrbase --port=4300
OpenShift's router picks this up and assigns a hostname under the cluster's wildcard domain (typically buttrbase-buttrbase.apps.YOUR_CLUSTER.example.com). Retrieve the hostname:
oc -n buttrbase get route buttrbase -o jsonpath='{.spec.host}'
If you need TLS (you do in production), annotate the Route for edge termination:
oc -n buttrbase annotate route buttrbase \
haproxy.router.openshift.io/timeout=60soc -n buttrbase patch route buttrbase \
-p '{"spec":{"tls":{"termination":"edge","insecureEdgeTerminationPolicy":"Redirect"}}}'
For a custom hostname, pass --hostname=buttrbase.example.com to oc expose or edit the Route spec directly. The OpenShift router handles the certificate via its wildcard cert or a custom cert you attach to the Route.
8. Verify
Check rollout, then probe the health endpoint through both the port-forward (direct) and the Route (external path):
oc -n buttrbase rollout status deploy/buttrbase --timeout=120sDirect probe via port-forward
oc -n buttrbase port-forward svc/buttrbase 4300:4300 &
curl -fsS http://localhost:4300/health && echo " ✓ healthy"External probe through the Route
ROUTE=$(oc -n buttrbase get route buttrbase -o jsonpath='{.spec.host}')
curl -fsS "https://${ROUTE}/health" && echo " ✓ route healthy"
Confirm the deployment mode round-tripped:
oc -n buttrbase exec deploy/buttrbase -- printenv BUTTRBASE_RUST_DEPLOYMENT_MODE
→ single-tenant-managed
If rollout status hangs, the pod is almost always failing readiness because a secret value is wrong or the database is unreachable. Read the logs:
oc -n buttrbase logs deploy/buttrbase --tail=50
Two failures dominate first installs: a database-url the pod's network cannot reach (readiness probe never passes), and an encryption-key that is not 32 bytes hex (the backend refuses to start). Both appear in the first ten log lines.
---
Path B — Package as a Helm-based Operator for OperatorHub (forward-looking)
> Forward-looking. There is no operator source in this repo today. This section describes the pattern and points at Red Hat's tooling. Do not attempt to run operator-sdk or opm commands against this repo — the scaffolding does not exist yet.
9. The Helm-based Operator pattern
Red Hat's operator-sdk supports a Helm operator scaffold: you give it a Helm chart and it generates a controller that watches a custom resource (e.g., ButtrBase) and drives helm upgrade --install whenever the spec changes. No Go code is required for the operator itself — the chart is the operator's reconciliation logic.
The rough path, described rather than commanded:
1. Scaffold the operator using operator-sdk init --plugins helm and operator-sdk create api, pointing it at the chart at deploy/helm/buttrbase-backend-rust/. The scaffold produces a controller image and a set of bundle manifests.
2. Define the CRD (ButtrBase kind) whose spec maps to the chart's values structure — topology mode, tenant slug, secret ref names. A customer creates one ButtrBase object and the operator drives the install.
3. Build and push the operator image to a registry reachable by OpenShift (or to registry.connect.redhat.com for certified operators).
4. Generate the OLM bundle (operator-sdk bundle generate): a ClusterServiceVersion (CSV), the CRD manifests, and metadata/annotations.yaml. The CSV is the OperatorHub listing — it declares what the operator installs, what permissions it needs, and what upgrade paths it supports.
5. Validate the bundle with operator-sdk bundle validate and run the Operator SDK's scorecard tests against a live cluster.
6. Submit to OperatorHub via Red Hat's certified-operators pipeline (a PR against the certified-operators GitHub repository). Red Hat reviews the CSV and bundle for compliance before the listing goes live.
The authoritative references for each of these steps are:
10. What the OLM install experience looks like for a customer
Once a bundle is listed on OperatorHub and the cluster administrator approves the operator:
1. The admin finds ButtrBase* in the OperatorHub tab of the OpenShift web console and clicks *Install.
2. OLM pulls the operator image and installs the ButtrBase CRD cluster-wide.
3. A developer creates a ButtrBase object in their namespace — specifying topology mode, tenant slug, and secret ref names in the object's spec.
4. The operator watches for the object and runs the chart, producing the same Deployment, Service, and ConfigMap that Path A produces.
5. Upgrades are managed by OLM: the admin approves a new operator version in the console; OLM rolls it out cluster-wide without manual helm upgrade commands.
The underlying chart is identical to what Path A installs. The operator is a thin controller that drives it declaratively.
---
Done
You have either a running ButtrBase backend on OpenShift installed from the committed chart (Path A), or a clear picture of what the certified operator path requires (Path B). From here:
07-deploy-with-helm.md is the ground truth for chart behavior, values semantics, and secret layout. If anything in the install behaves unexpectedly, start there.09 covers the same chart on EKS with AWS Marketplace entitlement wiring.10 covers the Managed Application and AKS path.