OpenShift / OKD
Installing Orbtrace on OpenShift under the default restricted-v2 SCC — the values-openshift overlay, the one node-level prerequisite (kernel settings via the Node Tuning Operator), the Doris operator PSA + SCC, and Route-based ingress.
Orbtrace's Helm chart runs on OpenShift under the default restricted-v2 Security Context Constraint (SCC) — no oc adm policy add-scc-to-user, no custom SCC — once you install with the OpenShift overlay. There is no Caddy on the Kubernetes path (Caddy is the single-host Docker Compose reverse proxy only); the OpenShift Router terminates TLS via a Route.
Read this alongside Install with Helm (the general Helm steps) and Setting up Doris (the telemetry store, which the chart never deploys).
This page follows the order you actually run things in: node kernel settings → Doris → registry login & your values file → helm install with the overlay → first sign-in.
1. The one node-level prerequisite — kernel settings
Doris BE needs vm.max_map_count >= 2000000 plus the two inotify limits from Setting up Doris — Requirements. These are node kernel settings, not something an unprivileged pod can change — the same requirement Elasticsearch / OpenSearch carry on OpenShift. It is not a separate Doris install; it is one cluster-admin step, and it comes first because the Doris BE you start next won't run without it.
The tool for this is the Node Tuning Operator. Check it is on your cluster before applying anything — the Tuned resource type below only exists when the operator is installed:
oc get crd tuneds.tuned.openshift.ioExpect one line naming the CRD. Real OpenShift ships the operator by default; OpenShift Local (CRC) and some minimal OKD builds don't. If this returns NotFound, skip the YAML below — oc apply cannot work without the operator — and jump straight to No operator on this cluster? at the end of this step.
Operator present? Apply the profile once:
apiVersion: tuned.openshift.io/v1
kind: Tuned
metadata:
name: doris-node-sysctls
namespace: openshift-cluster-node-tuning-operator
spec:
profile:
- name: doris-node-sysctls
data: |
[main]
summary=Kernel settings for Apache Doris BE
[sysctl]
vm.max_map_count=2000000
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=1048576
recommend:
- match:
- label: node-role.kubernetes.io/worker
priority: 20
profile: doris-node-sysctlsApplysave as doris-node-tuning.yaml, then runoc apply -f doris-node-tuning.yaml
No operator on this cluster? Set the sysctls on the nodes directly
Without the Node Tuning Operator the cluster does not know the Tuned resource type at all, so the oc apply above fails with no matches for kind "Tuned" in version "tuned.openshift.io/v1" / ensure CRDs are installed first. That error means exactly one thing: use this fallback instead. These are per-node (machine) settings, not per-pod — write them on each node Doris BE can land on:
# 1. List the candidate nodes — the NAME column is what you need.
# (OpenShift Local/CRC has exactly one node.)
oc get nodes -l node-role.kubernetes.io/worker
# 2. Apply on every worker in one shot:
for n in $(oc get nodes -l node-role.kubernetes.io/worker -o jsonpath='{.items[*].metadata.name}'); do
oc debug node/$n -- chroot /host sysctl -w \
vm.max_map_count=2000000 \
fs.inotify.max_user_instances=8192 \
fs.inotify.max_user_watches=1048576
done
# 3. Verify one node:
oc debug node/<node-name> -- chroot /host sysctl vm.max_map_count
# Expected: vm.max_map_count = 2000000This applies only to those nodes and only until they reboot — a node added later (autoscaling, replacement) comes up with the defaults and needs the same command. Pod restarts do NOT reset it. Fine for an evaluation. For a permanent setting without the operator, use a MachineConfig for the worker pool (or a /etc/sysctl.d drop-in on each node).
Scope the match to the nodes Doris BE lands on if you taint/label a storage pool. With this in place, the BE pods run unprivileged under restricted-v2. (Running Doris off-cluster instead? Then this step doesn't apply to your OpenShift nodes — skip to the next section.)
2. Doris on OpenShift
The chart does not deploy Doris (doris.mode: external). Run it with the doris-operator and point the chart at the operator-managed FE Service — the full walkthrough (operator install, the reference DorisCluster CR, verification) is in Setting up Doris. Doris must be up before you install the chart: the Orbtrace pod stays un-Ready until it can reach a live FE/BE.
The OpenShift-specific bit: two security defaults block the operator's pods. The operator injects a privileged default-init container into its BE pods (it raises vm.max_map_count) — the default namespace Pod Security Admission (restricted/baseline) rejects any privileged container, so the BE pod is never admitted and the cluster stays initializing. And the stock apache/doris images run as root — under restricted-v2's arbitrary UID the FE crashloops with start_fe.sh: Permission denied, which the anyuid SCC resolves. Create the Doris namespace, relax its PSA, and grant both SCCs (one time, before applying the DorisCluster CR — the block is idempotent; without the create line the rest fails with namespaces "doris" not found):
oc create namespace doris --dry-run=client -o yaml | oc apply -f -
oc label namespace doris pod-security.kubernetes.io/enforce=privileged --overwrite
oc adm policy add-scc-to-user privileged -z default -n doris
oc adm policy add-scc-to-user anyuid -z default -n dorisSkip it and the operator log reads would violate PodSecurity "…": privileged (container "default-init" must not set securityContext.privileged=true), oc get doriscluster never leaves initializing, and the Orbtrace app crashloops on the missing BE. Setting vm.max_map_count node-wide (step 1) is still recommended, but it does not remove these grants — the operator injects the privileged init regardless. To avoid the privileged init and both grants entirely, run Doris off-cluster (VM/managed, doris.mode: external) — the recommended OpenShift path.
3. Registry login, the chart, and your values file
The chart and the image are private GHCR packages. Log in with the per-customer token Nivorbit gives you, then create the pull secret the chart will use. <token> and <username> appear more than once — replace every occurrence before running:
echo "<token>" | helm registry login ghcr.io -u <username> --password-stdin
oc create namespace orbtrace
oc -n orbtrace create secret docker-registry ghcr \
--docker-server=ghcr.io --docker-username=<username> --docker-password=<token>helm registry login authenticates the Helm CLI to pull the chart; the Secret + global.imagePullSecrets (in the values file below) let the runtime pods pull the image.
Pull and unpack the chart — the OpenShift overlay and the sizing presets ship inside it, and helm show values can't hand you those files:
helm pull oci://ghcr.io/nivorbit/charts/orbtrace --version 2.1.0 --untarYou don't copy the whole values.yaml — Helm applies the chart's own defaults as the base. Write a small my-values.yaml next to the unpacked orbtrace/ directory with only the keys you change:
global:
imagePullSecrets: [{ name: ghcr }] # the secret created above
# No orbtrace.ingress on OpenShift — the overlay replaces the Ingress with a
# Route. Hostname and OAuth2 URLs need nothing by default (the chart derives
# them at install time); the next subsection covers custom hosts + the fallback.
# Doris — with the reference operator DorisCluster (section 2) the defaults
# already match; uncomment only what differs on your side:
# doris:
# host: <your-doris-fe-host> # a VM / managed FE, or non-default operator names
# password: <doris-root-password> # only if your Doris has one — a fresh install has none
# # Single-BE Doris (evaluation)? Also uncomment BOTH lines below, or the app
# # aborts at boot ("replication num … available backend num is 1") — tables
# # are created at replication factor 2 by default, which needs ≥2 live BE.
# # Never in production.
# allowSingleReplica: true
# profile: { replicationNum: 1 }
postgres:
password: <postgres-password> # required — the chart refuses to install if emptyOptional keys — orbtrace.licenseKey (blank = free COMMUNITY), orbtrace.oidc.* (SSO), orbtrace.ai.* (AI provider), doris.profile.activeProfile (small / medium / large) — are documented on Install with Helm and in the unpacked orbtrace/values.yaml; none is needed for a first install.
Route hostname & external URL
Nothing to set here by default. Leave openshift.route.host empty and the chart works it out at install time: it reads the cluster's apps domain (ingresses.config.openshift.io/cluster), mints the Route host orbtrace-<namespace>.<apps-domain>, and derives the OAuth2 sign-in URLs from that same host — login works with zero configuration (chart ≥ 2.0.12).
Want a specific address instead? Set a custom host — everything still derives from it:
openshift:
route:
host: orbtrace.your-company.com # optional; leave unset for orbtrace-<namespace>.<apps-domain>A custom host does not have to mean DNS work: any name under the cluster's apps-domain is already covered by the Router's wildcard DNS — say orbtrace.apps.<cluster-domain>, without the namespace part the generated one carries. Only a name outside the apps-domain (like orbtrace.your-company.com) needs a DNS record pointed at the Router.
TLS termination defaults to edge; reencrypt and passthrough are available via openshift.route.tls.termination.
| Your choice | What you must set |
|---|---|
Default — openshift.route.host left unset | Nothing: host and OAuth2 URLs auto-derive from the cluster's apps domain |
| Custom host | Just openshift.route.host — the URLs derive from it |
Sign-in redirects to localhost:8080 (ERR_CONNECTION_REFUSED)?
When this happens. Normally never — during a real helm install the chart reads the cluster's apps domain and sets the sign-in address itself. It could not do that only in two cases:
- the release was rendered with
helm templateor--dry-runinstead of a real install, or - the user who ran
helm installhad no permission to read the cluster Ingress config (ingresses.config.openshift.io/cluster).
The install itself still completes — only sign-in is broken.
The fix — two commands. Read the address the Router actually gave your Route, then write it into the release:
HOST=$(oc -n <namespace> get route orbtrace -o jsonpath='{.spec.host}')
helm upgrade orbtrace ./orbtrace -n <namespace> \
-f orbtrace/values-small.yaml -f my-values.yaml -f orbtrace/values-openshift.yaml \
--set openshift.route.host=$HOSTSign in again — the browser is now redirected to https://$HOST. (Alternatives: setting orbtrace.frontendUrl/backendUrl to https://$HOST does the same; a no-reinstall env patch is under Troubleshooting sign-in.)
Two naming facts worth knowing, no action needed:
- There is only ever one hostname. Orbtrace serves the UI and the API from the same origin — you never configure a second address.
- The generated name starts with
orbtracebecause the Route is named after the Helm release. The in-cluster workloads keep their component names (orbtrace-app,orbtrace-postgres,orbtrace-valkey), but those never appear in any URL.
Upgrading from chart 2.0.10 or older? The generated address changes
Older charts named the Route orbtrace-app, so a generated hostname read orbtrace-app-<namespace>.…. The upgrade renames the Route to orbtrace, and the generated hostname changes with it — update bookmarks and anything pointing at the old address. What you must do depends on one question:
- You set a custom
openshift.route.host? Nothing — your address is yours and stays. - You use the generated host? The new address and the sign-in URLs derive automatically after the upgrade. One cleanup first: if your values file still carries hand-set
orbtrace.frontendUrl/backendUrllines (the old chart required them), delete those lines before upgrading — hand-set values override the automatic ones and would keep sign-in pointing at the old, now-dead address.
4. Install with the overlay
Run this in the directory where you unpacked the chart (section 3) — ./orbtrace, the -f paths and your my-values.yaml resolve relative to it. Layer, in order: the sizing preset, your overrides, and the OpenShift overlay last (later -f files win, and the overlay's UID handling must win):
# Sizing preset: values-small | values-medium | values-large
helm install orbtrace ./orbtrace -n orbtrace \
-f orbtrace/values-small.yaml -f my-values.yaml \
-f orbtrace/values-openshift.yamlYour actual settings stay in my-values.yaml (previous section); the overlay layered last only adds the SCC / Route / UID handling on top — it holds none of your values.
What the overlay changes
| Concern | Default chart | OpenShift overlay |
|---|---|---|
| Pod UID | fixed runAsUser (10001 / 1000 / 999) + fsGroup | omitted — the SCC injects a UID from the namespace's pre-allocated range |
| Bundled pgvector | runs as the image's UID 999 | a passwd-shim init container injects an /etc/passwd entry for the SCC-assigned UID, so the postgres binary's getpwuid() succeeds — pgvector boots under an arbitrary UID with no anyuid grant |
| Routing | Ingress | native Route (the Router terminates TLS) |
| Doris BE sysctl | privileged init raises vm.max_map_count | omitted — you set it node-wide (step 1) |
A hardcoded UID outside the namespace's range is exactly what restricted-v2 admission rejects, which is why the overlay omits it rather than picking a "safe" number. If you run external Postgres (postgres.mode: external) you avoid the bundled-pgvector UID concern entirely — there's no in-cluster database pod for the SCC to constrain.
Expected (harmless) Postgres log line on OpenShift
On first boot the bundled Postgres prints chmod: changing permissions of '/var/run/postgresql': Operation not permitted. This comes from the upstream postgres image's entrypoint, which chmods its socket directory on startup — under the arbitrary SCC-assigned UID it can't change ownership, so the line is printed and skipped (|| :). It is not introduced by the Orbtrace chart, and Postgres starts normally: the very next log lines are database system is ready to accept connections. No action needed.
5. Signing in — the break-glass admin, then SSO
A fresh install signs you in out of the box: the chart provisions a break-glass local admin (orbtrace.bootstrapAdmin, on by default) with a generated, per-install password stored in a Secret — there is intentionally no fixed default password (a shared built-in credential on every install would be a critical vulnerability). Read it after install (the helm install output prints this too):
oc -n <namespace> get secret <release>-bootstrap-admin \
-o jsonpath='{.data.password}' | base64 -dSign in as admin@orbtrace.local (or your orbtrace.bootstrapAdmin.username) — you will be required to set a new password before Orbtrace issues a session. Then wire SSO for production (GOOGLE_ / MICROSOFT_ / GITHUB_CLIENT_ID + _SECRET, or a generic OIDC issuer — Authentication); the IdP handles MFA, lockout, and rotation. Keep the local admin as break-glass, or disable it for a pure-SSO page with orbtrace.bootstrapAdmin.enabled=false. Lost the changed password? See the reset knob in Authentication.
Troubleshooting sign-in
"This site can't be reached" — the address bar is on localhost:8080/oauth2/authorize
The app didn't know its external URL, so OIDC discovery advertised the in-pod address. On chart ≥ 2.0.12 this normally cannot happen — the chart derives the host automatically at install time. You see it when that auto-derive couldn't run: the release was rendered with helm template / --dry-run, or the installing user lacked read access to the cluster-scoped Ingress config (ingresses.config.openshift.io/cluster), and no openshift.route.host / orbtrace.frontendUrl+backendUrl was set by hand (section 3). The durable fix is one command — pin the host the Router generated and upgrade:
HOST=$(oc -n <namespace> get route orbtrace -o jsonpath='{.spec.host}')
helm upgrade orbtrace ./orbtrace -n <namespace> \
-f orbtrace/values-small.yaml -f my-values.yaml -f orbtrace/values-openshift.yaml \
--set openshift.route.host=$HOSTTo unblock a running deploy without a Helm upgrade, patch the env and let it roll — note this is temporary, the next helm upgrade reverts it:
oc set env deploy/<release>-app -n <namespace> \
ORBTRACE_BACKEND_URL=https://<your-route-host> \
ORBTRACE_FRONTEND_URL=https://<your-route-host>
oc rollout status deploy/<release>-app -n <namespace>Confirm the fix — the discovery authorization_endpoint must be your Route host, not localhost:
oc exec deploy/<release>-app -n <namespace> -- \
wget -qO- http://localhost:8080/.well-known/openid-configurationThe sign-in page says "No login providers are configured"
You disabled the break-glass admin (orbtrace.bootstrapAdmin.enabled=false) without wiring SSO — or you're on a pre-2.0.4 chart, which had no break-glass default. Either configure SSO (Authentication) or re-enable the bootstrap admin; to unblock a running deploy without reinstalling:
oc set env deploy/<release>-app -n <namespace> \
ORBTRACE_BOOTSTRAP_ADMIN_USERNAME=admin@your-company.com \
ORBTRACE_BOOTSTRAP_ADMIN_PASSWORD='<a-strong-password>'
oc rollout status deploy/<release>-app -n <namespace>Then reload the page and sign in with those credentials (you'll be asked to set a new password on first login). oc set env is wiped by the next helm upgrade — prefer the chart values (orbtrace.bootstrapAdmin.*) to make it survive upgrades.
Air-gapped OpenShift
Mirror the GHCR images and the OCI Helm chart into your internal registry and point global.imageRegistry at it. See Air-gapped operation for the full offline flow.
Next steps
Once the app is Ready, wire your telemetry in — stand up an OTel Collector, or add dorisexporter to your existing OTel pipeline (Integration patterns) — then finish with the post-install checklist.