Skip to content

OpenShift Installation#

Install Stakater Cloud Orchestrator on OpenShift using the ksp up command.

Prerequisites#

Ensure you have completed the prerequisites checklist before starting.

Step 1: Authenticate to OpenShift#

Configure your local kubeconfig to point to the target cluster:

oc login https://api.your-cluster.example.com:6443

# Verify access
oc whoami
oc get nodes

Step 2: Prepare Claim Files#

This guide targets variant: hosting — the full greenfield deployment that installs the entire platform foundation plus the SCO layer on a base OpenShift cluster. You author two claim files and adjust every <...> placeholder for your environment.

KubeStackConfig Claim (kubestack-config-claim.yaml)#

Applied first. Carries cluster identity, networking, reserved namespaces, and per-cluster knobs.

apiVersion: cloud.stakater.com/v1alpha1
kind: KubeStackConfig
metadata:
  name: <cluster-slug>-config              # convention: <cluster-slug>-config
  labels:
    crossplane.io/composite: <cluster-slug>-config
spec:
  managementPolicies:
    - '*'
  parameters:
    # Cluster identity
    platform: ocp
    variant: hosting
    name: <cluster-slug>                   # short identifier, e.g. eu-1-ndskuue5
    domain: apps.<cluster-domain>          # wildcard apps domain, e.g. apps.eu-1.ndskuue5.example.com

    # Reserved namespaces (keep the defaults unless you have a reason to change them)
    namespaces:
      logging: logging
      monitoring: monitoring
      platform: platform-system

    # Network — mirror `oc get network cluster -o yaml`
    network:
      clusterDomain: cluster.local
      podCIDR: 10.128.0.0/14
      serviceCIDR: 172.30.0.0/16

    # Hosted-cluster public DNS / routing — environment-specific (see note below)
    openshiftCluster:
      public:
        baseUrl: "public.<cluster-domain>"
        dnsSplit:
          enabled: true
          zones:
            - "public.<cluster-domain>"
          upstreams:
            - "<upstream-dns-1>"
            - "<upstream-dns-2>"
        openbsdUnbound:
          enabled: true
          openbsdHosts:
            - "<router-host-1>"
            - "<router-host-2>"
          routerPodIPs:
            - "<router-pod-ip>"
        azuread:                           # omit this block if you do not use Azure AD
          pimEligibleMembers:
            - objectId: <azure-object-id>
              assignmentType: member
              justification: <justification>
  providerConfigRef:
    name: kubernetes-provider
Field Required Description
parameters.name Cluster slug — short identifier; appears in resource names and labels. Use it consistently.
parameters.variant hosting for a hosting-cluster install.
parameters.platform ocp for OpenShift.
parameters.domain Wildcard apps domain — apps.<cluster-name>.<domain>.
parameters.namespaces.{platform,monitoring,logging} Reserved namespace names (defaults: platform-system / monitoring / logging).
parameters.network.{clusterDomain,podCIDR,serviceCIDR} Mirror the cluster's Network CR (oc get network cluster -o yaml).
parameters.openshiftCluster.public.baseUrl DNS subdomain for hosted-cluster public access (the split-DNS source).
parameters.openshiftCluster.public.dnsSplit Upstream DNS zones for hosted-cluster public resolution.
parameters.openshiftCluster.public.openbsdUnbound Router hosts that proxy DNS for hosted-cluster public access.
parameters.openshiftCluster.public.azuread.pimEligibleMembers Azure AD principals (object IDs) granted PIM-eligible access. Omit if not using Azure AD.

Note

The openshiftCluster.public block (split-DNS, OpenBSD-unbound, Azure AD) depends on your hosting cluster's networking and identity environment. Confirm the upstream DNS, router, and identity values with your platform team, and drop any sub-block that does not apply to your setup.

KubeStackPlus Claim (kubestack-plus-claim.yaml)#

Applied second — it triggers the platform composition. For variant: hosting this claim is minimal; almost all configuration flows through KubeStackConfig.

apiVersion: cloud.stakater.com/v1alpha1
kind: KubeStackPlus
metadata:
  name: <cluster-slug>-package             # convention: <cluster-slug>-package
  labels:
    crossplane.io/composite: <cluster-slug>-package
spec:
  managementPolicies:
    - '*'
  parameters:
    platform: ocp
    variant: hosting
    server: 'https://kubernetes.default.svc'   # local hosting cluster API
  providerConfigRef:
    name: kubernetes-provider
Field Required Description
metadata.name Claim name. Convention: <cluster-slug>-package.
parameters.variant hosting — must match the KubeStackConfig variant.
parameters.platform ocp.
parameters.server ArgoCD Application destination API server. For hosting, the local cluster API (default https://kubernetes.default.svc).
parameters.excludedAddons Optional list of addon names to exclude (see below).
spec.providerConfigRef.name kubernetes-provider — the CLI installs this provider if it is missing.

Excluding Addons#

If you need to exclude specific addons from the deployment, use the excludedAddons field:

spec:
  parameters:
    variant: hosting
    platform: ocp
    excludedAddons:
      - kcp-operator
      - kcp-operator-config
      - kcp-core-workspaces
      - kcp-services

Note

Excluding an addon also transitively excludes any addons that depend on it.

Note

Example claim files are available in the examples/ directory of the KubeStack+ CLI repository.

Step 3: Prepare Registry Credentials#

SCO components are pulled from Stakater's private registry, so a registry-secret file is required. Use the username and token Stakater provided — request them from sales@stakater.com if you don't have them yet (see Prerequisites).

Create registry-secret.yaml:

registry:
  url: "ghcr.io"
  gitopsChartsUrl: "ghcr.io/stakater"
  username: "<your-username>"
  password: "<your-token>"     # use either password or token; token wins if both are set

ksp up uses this file to log in to the registry and to create the in-cluster pull secrets that Crossplane and the composition functions need. It is not referenced from the claim files — it is passed on the command line via --registry-secret (Step 4).

Step 4: Run Installation#

ksp up operates on your current oc/kubectl context, so confirm it points at the target cluster first (oc whoami --show-server). Then run it with both claim files and your registry secret — -c (KubeStackConfig) is applied first, then -f (KubeStackPlus):

ksp up \
  -c kubestack-config-claim.yaml \
  -f kubestack-plus-claim.yaml \
  --registry-secret ./registry-secret.yaml

By default ksp up asks you to confirm the target cluster before making changes. For non-interactive runs (e.g. CI/CD), add --no-prompt:

ksp up -c kubestack-config-claim.yaml -f kubestack-plus-claim.yaml \
       --registry-secret ./registry-secret.yaml --no-prompt

To install prerequisites only without applying claims (useful for validating the cluster first):

ksp up --dev-mode

Useful Flags#

Flag Description
--trace Start an interactive trace session after applying claims to monitor progress
--dry-run ./output Template charts to a folder instead of installing (for review)
--extra-secrets secrets.yaml Create additional Kubernetes secrets during installation
--extra-charts charts.yaml Path to an extra charts definition file for additional Helm charts
--no-prompt Skip confirmation prompts (for CI/CD pipelines)
--no-detection Skip capability detection phase entirely (assumes greenfield)
--timeout 10m Increase the per-chart readiness timeout (default: 5m)
--dev-mode Install prerequisites only without applying claims
--kubecontext <name> Override the current kube context for all operations
--argocd-namespace <ns> ArgoCD namespace for repository secret (required in brownfield when ArgoCD is pre-existing)
--force Force installation even if KubeStackConfig claim already exists

Step 5: Monitor Installation#

The command outputs progress as it runs:

✓ Checking cluster capabilities...
✓ Installing Crossplane...
✓ Installing provider-kubernetes...
✓ Installing Crossplane functions...
✓ Creating ksp-system namespace...
✓ Applying kubestack-config claim...
✓ Applying kubestack-plus claim...
✓ Waiting for components to be ready...

Installation complete!

Step 6: Verify Installation#

Once the command completes, verify the platform is healthy:

# Core components
oc get pods -n ksp-system
oc get pods -n crossplane-system

# Confirm claims are ready
oc get kubestackconfig -n ksp-system
oc get kubestackplus -n ksp-system

# Check installed solution APIs are available
oc get xrds

Brownfield Clusters#

If your cluster already has some components installed, ksp up handles continuation automatically:

ksp-system KubeStackConfig KubeStackPlus Action taken
Not installed Install ksp-system, then apply both claims
Installed Not present Not present Apply both claims
Installed Present Not present Apply KubeStackPlus only
Installed Present Present Already complete — no action

If the cluster is in a partial state (some prerequisites missing), the command will report what is missing and exit without making changes.

Troubleshooting#

Check claim status#

oc describe kubestackconfig <name> -n ksp-system
oc describe kubestackplus <name> -n ksp-system

Check Crossplane logs#

oc logs -n crossplane-system -l app=crossplane
oc logs -n crossplane-system -l app.kubernetes.io/component=crossplane

Provider installation timeout#

Check that the cluster can pull images from the required registries:

oc get providers.pkg.crossplane.io
oc describe provider provider-kubernetes

Storage issues#

oc get pvc -n crossplane-system
oc get sc

Registry authentication failures#

If Helm charts fail to pull from the OCI registry:

# Verify credentials locally
helm registry login ghcr.io -u <username> -p <token>

# Check if the chart is accessible
helm pull oci://ghcr.io/stakater/saap-catalog/charts/crossplane-operator --version <version>

Ensure the token has read access to ghcr.io/stakater/saap-catalog. If using --registry-secret, verify the file format matches the expected structure (see Step 3).

Helm chart readiness timeout#

If a chart times out during installation:

# Check Helm release status
helm list -n ksp-system
helm status <release-name> -n ksp-system

# Check pod status for the failing component
oc get pods -n <chart-namespace> -o wide
oc describe pod <pod-name> -n <chart-namespace>

Common causes include slow image pulls (especially on first install), insufficient node resources, or pending PVCs. Increase the timeout with --timeout 10m and retry.

Brownfield recovery#

If ksp up reports an invalid cluster state (e.g., KubeStackPlus exists without KubeStackConfig):

# Check what's currently installed
oc get kubestackconfig -n ksp-system
oc get kubestackplus -n ksp-system
helm list -n ksp-system

# If you need to start fresh, remove existing claims first
oc delete kubestackplus <name> -n ksp-system
oc delete kubestackconfig <name> -n ksp-system

Then re-run ksp up. Use --force to override an existing KubeStackConfig claim if needed.

DNS and TLS verification#

If ingress routes are not accessible after installation:

# Verify wildcard DNS resolves
dig *.apps.<cluster-name>.example.com

# Check ingress controller status
oc get ingresscontroller -n openshift-ingress-operator

# Check TLS certificate
oc get secret -n openshift-ingress

Wildcard DNS and TLS certificates must be configured before running ksp up, as several components create ingress routes during deployment.

Uninstalling SCO#

Warning

Uninstalling SCO removes all organisations, projects, and provisioned resources. Ensure you have backups before proceeding.

oc delete kubestackplus <name> -n ksp-system
oc delete kubestackconfig <name> -n ksp-system
oc delete namespace ksp-system

What's Next?#