Skip to content

Install Rafay Controller on OpenShift (Air-Gapped)

This guide covers the prerequisites and steps required to install the Rafay Self-Hosted Airgapped Controller on an existing, operational OpenShift cluster using radm. It assumes the OpenShift cluster is already installed and operational, and is scoped exclusively to the Rafay controller installation.

Prerequisites

OpenShift cluster requirements

Requirement Details
OpenShift version 4.x (tested on 4.19.x, Kubernetes 1.32+)
Access level cluster-admin kubeconfig
Node configuration (HA) 3 control-plane nodes minimum (compact), or 3 control-plane + 3 workers (recommended for production)
Node configuration (POC / non-HA) Single-Node OpenShift (SNO) possible; 3-node compact recommended
Node OS RHCOS (immutable root filesystem — all host paths must be under /var; see OpenShift-specific overrides below)
Root disk per node 1000 GB SSD minimum
Ingress Support for Services of type LoadBalancer (cloud LB, or MetalLB on bare-metal OpenShift)
Resources (CPU / memory) See Node sizing below

Install host requirements

Item Details
OS Linux, with network access to the OpenShift API server and the registry
Tools required oc or kubectl, helm 3.x, jq (for the pull-secret merge)
Binary radm matching the bundle version
Bundle Rafay airgap tarball downloaded and extracted (see Controller installation → Download and extract the bundle)
Disk space Minimum 200 GB free total: 100 GB for the downloaded + extracted bundle, and a minimum of 100 GB free under the unarchive path (default /tmp) to unarchive image tarballs

Set the working directory for all commands used in this guide:

cd /path/to/rafay-airgapped-controller-<version>-openshift

Image registry (air-gapped)

A container registry reachable by the cluster is required (JFrog Artifactory, Harbor, Nexus, ECR, etc.) — this is what the Rafay images are pushed to and pulled from in the air-gapped install. Before running any radm install phase, the registry must be trusted by OpenShift and the Rafay images must be pushed to it (covered in Controller installation → Push images to the registry).

1. TLS trust

  • Registry with a public CA certificate (e.g., DigiCert on JFrog Cloud) — no extra CA step needed.
  • Registry with a private/self-signed CA — add the CA to OpenShift via image.config.openshift.io/cluster spec.additionalTrustedCA, or configure additionalTrustBundle at cluster install time.

2. Registry authentication — merge into the global pull secret

export KUBECONFIG=/path/to/kubeconfig
REG=myregistry.example.com
REG_USER='<username>'
REG_PASS='<password-or-token>'

# Extract current pull secret
oc get secret/pull-secret -n openshift-config \
  -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d > /tmp/pull.json

# Merge new registry credentials
AUTH=$(printf '%s:%s' "$REG_USER" "$REG_PASS" | base64 -w0)
jq --arg reg "$REG" --arg auth "$AUTH" \
  '.auths[$reg]={auth:$auth}' /tmp/pull.json \
  > /tmp/pull-merged.json

# Apply merged pull secret
oc set data secret/pull-secret -n openshift-config \
  --from-file=.dockerconfigjson=/tmp/pull-merged.json

rm -f /tmp/pull.json /tmp/pull-merged.json

# Wait for the Machine Config Operator to roll credentials to all nodes
oc get mcp   # wait until UPDATED=True, UPDATING=False
oc get nodes

Storage

The Rafay controller requires Kubernetes StorageClass resources for PVCs: one ReadWriteOnce (RWO) and one ReadWriteMany (RWX) class. Storage must be deployed on the cluster before installation — radm does not deploy storage on OpenShift. Any CSI-compliant backend can be used; install it by following the storage vendor's public documentation, for example:

Storage option Public documentation Typical classes (RWO / RWX)
Longhorn https://longhorn.io/docs/ (OCP install guide) longhorn / longhorn (RWX via share-manager)
OpenEBS https://openebs.io/docs/ openebs-hostpath / openebs-kernel-nfs
OpenShift Data Foundation (Ceph) Red Hat ODF documentation ocs-storagecluster-ceph-rbd / ocs-storagecluster-cephfs

These classes are referenced in config.yaml during configuration (Controller installation → Configure config.yaml):

spec:
  storage:
    external:
      enabled: true   # true = storage is pre-provisioned on the cluster (this guide).
      # radm skips its storage phase and uses the classes below.
      storageClass:
        readWriteOnce: "<your-rwo-sc>"
        readWriteOnceReplicated: "<your-rwo-sc>"
        readWriteMany: "<your-rwx-sc>"   # required when deployment.ha: true

DNS and ingress

Rafay exposes all traffic via the Istio ingress gateway (a Service of type LoadBalancer in istio-system). DNS records for the controller domain must point to the EXTERNAL-IP of that service. The IP is assigned during the dependency install phase, so prepare the DNS zone in advance; the records are created after that phase completes (Controller installation → Configure DNS with the Istio external IP).

A wildcard DNS record is recommended for the controller domain (replace rafay.example.com with your chosen domain):

*.rafay.example.com

If a wildcard is not possible, create the individual records below:

api.<rafay.example.com>
console.<rafay.example.com>
fluentd-aggr.<rafay.example.com>
ops-console.<rafay.example.com>
grafana.<rafay.example.com>
kibana.<rafay.example.com>
*.cdrelay.<rafay.example.com>
*.core-connector.<rafay.example.com>
*.core.<rafay.example.com>
*.infrarelay.<rafay.example.com>
*.kubeapi-proxy.<rafay.example.com>
*.user.<rafay.example.com>

Node sizing

The cluster must provide the following total allocatable capacity across schedulable nodes for the selected controller size (spec.deployment.size in config.yaml):

Size HA mode CPU (minimum) Memory (minimum) Root disk per node
POC Non-HA only 32 CPU 64 GB 1000 GB SSD
S HA only 32 CPU 64 GB 1000 GB SSD
M HA only 48 CPU 96 GB 1000 GB SSD
L HA only 96 CPU 192 GB 1000 GB SSD
XL HA only 128 CPU 192 GB 1000 GB SSD

Controller installation

The following installs the Rafay Self-Hosted Airgapped Controller: images are pulled from the release bundle and pushed to your own registry, and the cluster pulls exclusively from that registry — no images are fetched from the public internet during install.

All commands are run from the install host, from the bundle directory.

1. Download and extract the bundle

sudo mkdir -p /opt/rafay && cd /opt/rafay

wget https://rafay-airgap-controller.s3.us-west-2.amazonaws.com/3.1/rafay-airgapped-controller-<version>-openshift.tar.gz

# Verify the md5sum of the downloaded package against the value
# provided with the release
md5sum rafay-airgapped-controller-<version>-openshift.tar.gz

# Untar the package
tar -xf rafay-airgapped-controller-<version>-openshift.tar.gz
cd rafay-airgapped-controller-<version>-openshift

# Install the radm binary and copy the config template
sudo cp ./radm /usr/bin/
cp config.yaml-airgap-tmpl config.yaml
vi config.yaml

2. Configure config.yaml

Set the minimum required fields:

apiVersion: radm.k8s.io/v1beta1
kind: InitConfiguration
metadata:
  name: RafayAirGapController
spec:
  blueprintVersion: v3

  # External registry --- all chart images pull from here
  external-registry-endpoint: "myregistry.example.com/rafay-docker/rafay"

  deployment:
    ha: true          # true for S/M/L/XL; false only for POC
    size: "S"         # POC | S | M | L | XL

  kube-config: "/path/to/kubeconfig"   # OpenShift kubeconfig --- radm targets
                                        # the existing cluster (radm init is NOT used)

  storage:
    external:
      enabled: true
      storageClass:
        readWriteOnce: "<your-rwo-sc>"
        readWriteOnceReplicated: "<your-rwo-sc>"
        readWriteMany: "<your-rwx-sc>"

  metrics-server:
    external: true    # OpenShift already provides metrics-server

  repo:
    archive-directory: /path/to/rafay-airgapped-controller-<version>-openshift
    unarchive-path: /tmp   # needs minimum 100 GB free

  rafay-registry:
    registry-subpath: "rafay-docker"   # repository key/path in your registry;
                                        # images live at <endpoint>/<subpath>/rafay/<image>

  app-config:
    generate-self-signed-certs: true

  partner:
    star-domain: rafay.example.com   # your controller base domain

  super-user:
    user: [email protected]
    password: <base64-encoded-password>

Warning: Path alignment is critical. external-registry-endpoint must exactly match where radm registry sync (see Push images to the registry) pushes the images: <endpoint>/<registry-subpath>/rafay/<image>:<tag>. Any mismatch causes ErrImagePull or "manifest unknown" errors.

OpenShift-specific overrides

RHCOS has a read-only root filesystem, so hostPath volumes under /data or /storage will fail. Always use /var/... paths. Add these OpenShift-specific overrides under override-config:

override-config:
  localprovisioner.basePath: "/var/openebs/local"
  core-registry-path: "/var/registry"
  etcd-path: "/var/etcd"
  global.external_lb: "false"
  global.enable_hosted_dns_server: "false"

Export your kubeconfig before starting the install phases below:

cd /path/to/rafay-airgapped-controller-<version>-openshift
export KUBECONFIG=/path/to/kubeconfig   # or pass --kubeconfig on each command

3. Push images to the registry (radm registry sync)

Push the bundle image archives — downloaded in step 1, with no external network calls — into the registry configured in config.yaml. The --archive flags list the image tarballs from the bundle directory; --stage is the staging directory used to unarchive them (minimum 100 GB free).

JFrog / generic Docker registry:

./radm registry sync jfrog \
  --config config.yaml \
  --endpoint myregistry.example.com \
  --user "<your-registry-user>" \
  --password "<your-registry-token-or-api-key>" \
  --archive rafay-infra-images.tar.gz \
  --archive rafay-dep-images-v3.tar.gz \
  --archive rafay-core-images.tar.gz \
  --archive rafay-istio-1.27.2-images.tar.gz \
  --stage /tmp

AWS ECR:

./radm registry sync ecr \
  --config config.yaml \
  --endpoint <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com \
  --ecr-region <REGION> \
  --archive rafay-infra-images.tar.gz \
  --archive rafay-dep-images-v3.tar.gz \
  --archive rafay-core-images.tar.gz \
  --archive rafay-istio-1.27.2-images.tar.gz \
  --stage /tmp

Verify the sync:

oc run registry-pull-test --restart=Never \
  --image=myregistry.example.com/rafay-docker/rafay/<any-image>:<tag> \
  --command -- sleep 30

oc get pod registry-pull-test   # expect: Running
oc delete pod registry-pull-test

4. Install dependencies (radm dependency)

Installs the platform charts: Istio, cert-manager, kube-prometheus-stack, postgres, kafka, and other platform dependencies — all pulled from your registry.

sudo ./radm dependency \
  --config config.yaml \
  --kubeconfig $KUBECONFIG

On OpenShift, this phase automatically:

  • Detects OpenShift via the API (security.openshift.io)
  • Grants the anyuid and privileged SCCs to the Rafay namespaces
  • Skips haproxy-ingress and metrics-server (provided by the platform)
  • Skips uploading images to the local registry when external-registry-endpoint is set

Verify:

oc get pods -n istio-system
oc get pods -n cert-manager
oc get svc istio-ingressgateway -n istio-system   # note the EXTERNAL-IP for DNS (see "Configure DNS with the Istio external IP" below)

5. Install application — Rafay core (radm application)

Installs the Rafay controller components: Nexus, Dgraph, Kafka, paas-api, dashboard, and all core services. Allow approximately 20–30 minutes for all pods to become ready; pods may briefly restart during database migrations.

sudo ./radm application \
  --config config.yaml \
  --kubeconfig $KUBECONFIG

Verify:

oc get pods -n rafay-core
oc get pods -n rafay-core | grep -E 'nexus|dgraph|paas-api|dashboard'
helm list -n rafay-core

6. Configure DNS with the Istio external IP

Get the external IP assigned to the Istio ingress gateway, add it to the DNS records for the controller domain (see Prerequisites → DNS and ingress), and verify DNS resolution before proceeding.

# 1. Get the Istio gateway external IP
oc get svc istio-ingressgateway -n istio-system \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

# 2. Add/point the DNS records to this IP:
#    *.rafay.example.com → <EXTERNAL-IP>
#    (or the individual records listed under "Prerequisites → DNS and ingress")

# 3. Verify DNS resolves to the external IP
dig +short console.rafay.example.com
dig +short ops-console.rafay.example.com
# Both must return the Istio gateway EXTERNAL-IP before continuing

7. Upload cluster provisioning artifacts (radm cluster)

Required only if you plan to provision managed clusters from the Rafay console. Uploads cluster images and binary assets to the controller. DNS (see Configure DNS with the Istio external IP) must resolve before this step, since assets upload to ops-console.<star-domain>.

sudo ./radm cluster \
  --config config.yaml \
  --kubeconfig $KUBECONFIG \
  --fqdn rafay.example.com
# Replace rafay.example.com with your app-config.partner.star-domain value

Validation

Confirm the controller installed correctly and is functioning end-to-end.

1. Pod health

oc get pods -n rafay-core
oc get pods -n istio-system
# All critical pods should be Running or Completed (jobs)
oc get pods -A | grep -vE 'Running|Completed'

2. Helm releases

helm list -n rafay-core
# all releases should show STATUS: deployed

3. Ingress and DNS

# Get the Istio gateway external IP
oc get svc istio-ingressgateway -n istio-system -o wide

# DNS should resolve to that IP
dig +short console.rafay.example.com
dig +short ops-console.rafay.example.com

4. Image provenance (air-gapped check)

Confirm the running pods are pulling exclusively from your private registry and not from any public source:

oc get pods -n rafay-core -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | sort -u
# every image reference should be prefixed with your registry endpoint
# (<endpoint>/<registry-subpath>/rafay/...)

5. Access the Rafay console

Open the console in a browser via the Istio ingress:

https://console.<star-domain>

Use the Sign Up link to create the first organization for the controller and register a new account, then log in with the newly registered account. A successful login confirms the API, database, and UI layers are all functioning.

6. Cluster provisioning readiness (if applicable)

If Upload cluster provisioning artifacts was run, confirm the provisioning assets are reachable:

https://ops-console.<star-domain>