---
title: Red Hat OpenShift
description: "Install OpenLM Platform on Red Hat OpenShift, including ROSA and ARO: security context constraints, Routes, certificates, storage, and chart changes."
product: OpenLM Platform
---

Read this page before [step 1](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-1-prepare-cluster) to install OpenLM Platform on Red Hat OpenShift. OpenShift is Kubernetes with stricter defaults, and OpenLM Platform runs on it without special privileges: no `anyuid`, no privileged containers, and no cluster-admin for the standard component set. This page explains what's different, what the chart does about it, and the few extra steps you take.

## What's different on OpenShift, and how OpenLM Platform handles it

The following table shows what OpenShift does differently and how the OpenLM chart handles it:

| OpenShift does… | Effect on an ordinary chart | What the OpenLM chart does |
|---|---|---|
| Runs every pod under the **`restricted-v2` SCC**: a random user ID from the project's range, no root, no privilege escalation, all capabilities dropped | Pods with a fixed user ID (for example `1000`) are rejected | With `global.useOpenShift: true`, the chart **leaves the user and group IDs out**, so OpenShift assigns them. Every pod still declares `runAsNonRoot`, `seccompProfile: RuntimeDefault`, `allowPrivilegeEscalation: false` and drops all capabilities, which is what `restricted-v2` requires. A few components need their IDs set explicitly in this release (see [Values](#values)). |
| Brings traffic in with **Routes** and its own router | Ingress objects with controller-specific annotations don't behave as intended | With `useOpenShift: true`, the chart creates **Routes**: edge TLS at the router, HTTP-to-HTTPS redirect, HSTS, and security headers. |
| Often gives you **a project, not the cluster** | You can't label nodes, install CRDs, or create cluster roles | The chart needs none of that. Leave node placement off. |
| Often pulls images **through a mirror** | Image names must be rewritten or mirrored | OpenLM images use one registry path, `public.ecr.aws/r3q3q2f4`, and the API gateway also pulls from `docker.tyk.io`. Mirror both (see [Images](#images)). |
| Applies **LimitRanges and ResourceQuotas** per project | Pods that request less than the project's minimum are rejected, and big installations hit the quota | Plan the quota, and ask for the minimums to be removed: see [Resources and quotas](#resources-and-quotas). |

OpenLM Platform images run as an arbitrary user ID with group `0`, the OpenShift convention, with one exception in this release: `denialsaggregation`, the scheduled job that prepares the denials reports. Under an arbitrary user ID, each of its runs ends with `Error`. The rest of OpenLM Platform isn't affected, but the denials reports aren't updated.

## Before you start

Confirm these requirements:

| Item | Requirement |
|---|---|
| OpenShift | 4.18 or newer, including Red Hat OpenShift Service on AWS (ROSA) and Azure Red Hat OpenShift (ARO) |
| Your rights | `admin` on one project. No cluster-admin needed for the standard set. |
| Tested on | OpenShift 4.18 (ROSA) and the OpenShift 4.20 family (Kubernetes 1.33) in production |
| Cluster-level objects | None for the standard set. The optional admission webhook component creates cluster-level objects and needs a cluster admin. It isn't part of the standard set. |
| Storage | A **block** storage class: `gp3-csi` on ROSA, `managed-csi` on ARO, your CSI block class elsewhere (for example `thin-csi`, `ocs-storagecluster-ceph-rbd`). Many clusters default to a file-based class (NFS or SMB). Don't use it for the platform's volumes or for databases and Kafka: set `STORAGE_CLASS` to the block class explicitly. |
| DNS name | Either a name under the cluster's apps domain (`openlm.apps.<cluster-domain>`, simplest), or your own name with a certificate. See [Certificates](#certificates). |

In the worksheet, set `ON_OPENSHIFT="true"`. Leave `INGRESS_CLASS` at any value: OpenShift doesn't use it.

## Create the project

Create the project and read its user ID range:

```bash
oc new-project openlm
oc get project openlm -o jsonpath='{.metadata.annotations.openshift\.io/sa\.scc\.uid-range}{"\n"}'
```

The second command prints the project's user ID range, for example `1002360000/10000`. The script in [Values](#values) uses it.

## Values

After [step 4](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-4-values), generate a **second** values file for OpenShift. The commands load their variables from `openlm-install.env`, the file you created from the [installation worksheet](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/before-you-start/worksheet). You pass both files to Helm. Helm merges them, so nothing in the first file needs to change.

```bash
source ./openlm-install.env
UID_START="$(oc get project "$NS" -o jsonpath='{.metadata.annotations.openshift\.io/sa\.scc\.uid-range}' | cut -d/ -f1)"
F=openlm-values-openshift.yaml
cat > $F <<EOF
# OpenShift additions for $DOMAIN (project UID range starts at $UID_START)
global:
  useOpenShift: true             # Routes instead of Ingress; OpenShift assigns user IDs
  masterIngress: { enabled: false }
EOF
# Every Route carries the host name
for c in agentactivitymanagerui agentshubui alertsserviceui auditserviceui brokerhubui cloudbrokerui \
         complianceserviceui dbconfigurationtoolui denialsui donglemonitoringui dssui homepageui identity \
         identityui integrationui licenseaccesscontrolui licenseallocationsui licensefilesui licensemanagerui \
         licenseserversui licensesui navigationui notificationui personaldashboard processmanagerui \
         productsserviceui projectsserviceui samcatalogui \
         softwareassetmanagementui uiconfigurationserviceui usageui usersandgroupsserviceui vlmui; do
  printf '%s:\n  ingress: { hostname: %s }\n' "$c" "$DOMAIN" >> $F
done
# Components that set their own user IDs: give them IDs from the project's range
for c in licensefilesmanagement platformhealth; do
  printf '%s:\n  securityContext:\n    pod: { runAsUser: %s, runAsGroup: 0, fsGroup: %s, runAsNonRoot: true, seccompProfile: { type: RuntimeDefault } }\n' \
    "$c" "$UID_START" "$UID_START" >> $F
done
echo "written: $F"
```

Then install with **both** files. This replaces the command in [step 6](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-6-install):

```bash
helm upgrade --install openlm-platform openlm/openlm-platform --version "$OPENLM_VERSION" -n "$NS" \
  -f openlm-values.yaml -f openlm-values-openshift.yaml --timeout 60m
```

## Routes

The chart creates a Route for each web UI and for sign-in. Never change a Route's host on upgrade: it stays your `DOMAIN`. In this release, take three extra steps **after every installation and upgrade**.

**1. If you switched on data anonymization**, create its Route, which the chart doesn't create yet. Without it, that path shows the home page instead. The `oc delete route` lines apply to every installation. (The `/api` Route comes from the gateway release in [step 5](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-5-gateway).)

```bash
oc -n "$NS" create route edge openlm-anonymization --service=openlm-anonymizationui --hostname="$DOMAIN" \
   --path=/anonymization --insecure-policy=Redirect --dry-run=client -o yaml | oc -n "$NS" apply -f -
oc -n "$NS" annotate route openlm-anonymization --overwrite haproxy.router.openshift.io/rewrite-target=/
# Only if an earlier installation created these by hand: remove them
oc -n "$NS" delete route openlm-api openlm-dbconfigurationtool openlm-identity-alignment \
   openlm-process-sessions openlm-touch-point-events --ignore-not-found
```

:::note[Hand-made Routes during an upgrade]
If a Route you created by hand has the same name as one the chart now creates, the upgrade stops before it changes anything. Either delete the hand-made Route with the command in step 1, or let Helm take it over: add the label `app.kubernetes.io/managed-by=Helm` and the annotations `meta.helm.sh/release-name=openlm-platform` and `meta.helm.sh/release-namespace=openlm` to it, then run the upgrade again.
:::

**2. Let sign-in keep its path.** The sign-in Route must pass `/identity` through unchanged. Without this, sign-in fails with `409`:

```bash
oc -n "$NS" annotate route identity-ingress haproxy.router.openshift.io/rewrite-target-
```

**3. Check the Routes:**

```bash
oc -n "$NS" get routes -o custom-columns=NAME:.metadata.name,HOST:.spec.host,PATH:.spec.path | sort -k3
```

Every Route shows your `DOMAIN`, and the paths include `/`, `/api` and `/identity`.

## Certificates

Routes terminate TLS at the OpenShift router. The chart's Routes don't carry a certificate of their own, so **the router serves its default certificate**. The following table shows the three cases:

| Your `DOMAIN` | Router certificate | What to do |
|---|---|---|
| Under the apps domain (`openlm.apps.<cluster>`), and the router's certificate is from a public CA, the usual case on ROSA | Trusted | Nothing |
| Under the apps domain, router certificate from the cluster's own CA (the default on a new self-managed cluster) | **Not trusted by pods** | Ask the cluster admin to install a trusted default certificate on the router, or use a name with its own certificate |
| Your own name (`openlm.example.com`) | Doesn't cover it | Ask the cluster admin to serve a certificate for your name on the router (default certificate or a router shard), or add the certificate to each Route (`spec.tls.certificate` / `key`), which needs the `routes/custom-host` permission |

To check the certificate the pods see, run this command:

```bash
echo | openssl s_client -connect "$DOMAIN:443" -servername "$DOMAIN" 2>/dev/null | openssl x509 -noout -subject -issuer -enddate
```

The subject or alternative names must cover `DOMAIN`, and the issuer must be a CA your pods trust. [TLS certificates](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/concepts/tls-certificates) explains why this matters for sign-in.

You still create `openlm-lb-cert` in [step 3](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-3-secrets). OpenShift doesn't use it, but keeping it makes the secret set identical on every platform.

## Images

If the cluster can't pull from `public.ecr.aws`, mirror `public.ecr.aws/r3q3q2f4` into your registry and add an `ImageTagMirrorSet` (cluster admin), or set up a pull-through remote repository (Quay, Artifactory, Nexus). Image tags stay the same.

Pull-through registries often scan each new image on its first pull and hold it until the scan finishes, and public registries limit anonymous pulls. Pull the release's images through your registry **a day or more before** the installation or upgrade window. The image list is on the release's [release notes](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/release-notes/index) page. Give the remote repository credentials for the public registry.

An upgrade pulls dozens of new images at once. Check that the workers have free image storage before you upgrade. A worker that runs low on disk evicts pods (`DiskPressure`).

## Resources and quotas

Plan the project quota with these figures:

- The standard set **requests** about 10 vCPU / 22 GiB and has **limits** of about 55 GiB of memory in total. Projects often have a quota on **limits**, not requests. It must allow the limits, plus the API gateway, plus room for an upgrade, when old and new pods run side by side. Ask for `limits.cpu: 64`, `limits.memory: 128Gi`. The [Sizing](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/before-you-start/sizing) page has the details.
- A **LimitRange minimum** blocks most of the platform: most components request little (the smallest 5m CPU and 10 MiB of memory), and OpenShift **rejects** a pod whose request is below the minimum, with an event such as `minimum cpu usage per Pod is 100m`. Ask the cluster admin to remove the project's minimums, or to set them no higher than 5m CPU and 10 MiB. Defaults and maximums are fine.
- OpenLM Platform uses **4 PersistentVolumeClaims**, and the API gateway **3** more. A project with a PVC quota needs room for at least 7, plus the volumes of any data services you run in the project.

## Data services on OpenShift

Run MongoDB, Kafka and Redis **outside** the project (managed services or your platform team's), or with operators made for OpenShift (for example Strimzi or AMQ Streams for Kafka). `restricted-v2` rejects many generic charts, because they hard-code user IDs.

If Kafka runs in the project, set a size limit (`retention.bytes`) on every topic so retention can't fill its volume. Size the volume as described in [Sizing](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/before-you-start/sizing).

## Backups

CSI volume snapshots work for the platform's volumes: a `VolumeSnapshot` of each PVC in the project. Back up SQL Server, MongoDB and Kafka with their own tools.

## Checks specific to OpenShift

Run these commands. Each prints nothing on a healthy installation:

```bash
oc -n openlm get pods --no-headers | grep -vE "Running|Completed"                 # nothing
oc -n openlm get events --field-selector reason=FailedCreate | grep -i "unable to validate against any security context constraint"   # nothing
```

In this release, the first command also lists failed `openlm-denialsaggregation-…` job pods. They are expected, as [What's different on OpenShift](#whats-different-on-openshift-and-how-openlm-platform-handles-it) explains. Any other pod in its output needs attention.

A `FailedCreate` event that mentions the security context constraints names the component that still sets a fixed user ID. Add it to the second loop in [Values](#values) and run the installation again.
