Skip to Content
InstallationRunning on OpenShift

Running on OpenShift

TruStacks supports any conformant Kubernetes, and OpenShift is a first-class distribution. The runner installs under OpenShift’s default security policy with no extra grants, and what the agents write for an OpenShift cluster runs there unmodified.

OpenShift support arrives with runner 0.2.15. Older runners set a fixed user ID that OpenShift rejects, so the pod never starts.

What is different on OpenShift

Almost nothing you write by hand. One set of workload files serves both OpenShift and other Kubernetes; only a few cluster details change.

Other KubernetesOpenShift
Runner pod userFixed UID 10001Assigned by the SCC from the namespace’s range
Where ArgoCD Applications liveargocdopenshift-gitops
Namespace ArgoCD creates for the servicePlainLabelled argocd.argoproj.io/managed-by
ExposureIngressThe same Ingress; the router turns it into a Route

Install the runner

Use the same command as any cluster (Runner install). The chart detects OpenShift from the security.openshift.io/v1 API and adjusts itself:

  • It does not set runAsUser, runAsGroup or fsGroup. The restricted-v2 SCC assigns them, and rejects a pod that asks for its own.
  • It keeps runAsNonRoot, drops every capability, disallows privilege escalation, uses seccompProfile: RuntimeDefault and a read-only root filesystem.
  • No custom SCC and no anyuid grant are needed. Don’t add one.

Install into a namespace of your own, not an openshift-* namespace: Pod Security label sync is off there.

If you render the chart without a cluster to ask (helm template, or a GitOps tool that renders offline), say so explicitly:

--set openshift=true

openshift: auto is the default. Set false to force the vanilla behaviour on a cluster that exposes the OpenShift API.

Deploy keys work too

Under an arbitrary UID the process has no entry in /etc/passwd, and OpenSSH refuses to run for a user it cannot look up. The runner image handles this at start-up, so repositories you connect with an SSH deploy key clone normally. Repositories connected through the GitHub App use HTTPS and were never affected.

Deploy status

With observer.enabled=true, the runner watches ArgoCD Applications in openshift-gitops on OpenShift and argocd elsewhere. If your Applications live somewhere else, for example an upstream ArgoCD you installed yourself, set it:

--set observer.enabled=true --set observer.argocdNamespace=argocd

The RoleBinding is created in that namespace, so it has to exist.

Declare the cluster as OpenShift

In Applications, open the Application, and under Target clusters choose OpenShift as the distribution when you add the cluster. The chip then reads Kubernetes · OpenShift.

The distribution is a setting, not something TruStacks detects: it tells the agents which cluster details to write. A cluster without it is treated as other Kubernetes.

Two optional per-cluster values, set through the API today:

SettingDefaultUse it when
gitops_namespaceopenshift-gitops on OpenShift, else argocdYour Applications live in a namespace-scoped ArgoCD instance
argocd_projectdefaultYou scope teams with their own AppProject (you create it; TruStacks only names it)

What TruStacks writes for an OpenShift cluster

The ArgoCD Application

apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: payments-api namespace: openshift-gitops spec: project: default # source and destination as on any cluster syncPolicy: syncOptions: - CreateNamespace=true managedNamespaceMetadata: labels: argocd.argoproj.io/managed-by: openshift-gitops

Sync stays manual, and on a prod cluster manual sync is enforced by policy, as everywhere (Sync is manual).

CreateNamespace=true creates a plain Namespace, which skips your cluster’s project template (quotas, NetworkPolicies). If you want the template, create the project first; ArgoCD then uses it.

Workloads

Every Deployment the agents write carries the same baseline on every Kubernetes, and a policy rule (practice.k8s_workload_restricted) checks it before any pull request opens:

  • runAsNonRoot, seccompProfile: RuntimeDefault, no privilege escalation, all capabilities dropped;
  • no fixed runAsUser, runAsGroup or fsGroup;
  • CPU and memory requests and limits on every container (OpenShift projects often enforce a quota);
  • container ports above 1024.

The reference Dockerfiles run as a numeric non-root user in group 0 (USER 1001:0; .NET uses the image’s own 1654:0) and give group 0 the owner’s permissions on the app directory. OpenShift starts the container as an arbitrary UID in group 0, so the image can still write where it needs to. The same image runs unchanged elsewhere.

Exposure

With Helm, the chart includes an Ingress, off by default: the hostname is yours, and TruStacks never guesses one. Turn it on in the chart’s values (Kustomize: see below):

ingress: enabled: true host: payments.apps.example.com annotations: route.openshift.io/termination: edge

The router turns the Ingress into a managed Route. The termination annotation makes it terminate TLS at the router, and a TLS-enabled Ingress also gets HTTP redirected to HTTPS. Two things to know:

  • An unknown termination value is ignored and falls back to edge.
  • nginx.ingress.kubernetes.io/* annotations do nothing on the OpenShift router.

With Kustomize

Kustomize has no “off” switch, so the Ingress ships as an opt-in add-on: a Kustomize component at gitops/<application>/<service>/components/ingress/ (kustomization.yaml with kind: Component, and ingress.yaml). No overlay references it, so nothing in it deploys until you add it. On an OpenShift cluster the component’s Ingress carries route.openshift.io/termination: edge, so the router builds a TLS Route that redirects HTTP to HTTPS, as above.

To expose the service on a cluster, add this to that cluster’s overlay (overlays/<cluster>/kustomization.yaml), with your own host:

components: - ../../components/ingress patches: - target: {kind: Ingress, name: <service>} patch: |- - op: replace path: /spec/rules/0/host value: <your-host> # Only if your cluster needs one: # - op: add # path: /spec/ingressClassName # value: <class>

The component’s host is the placeholder change-me.invalid, which can never resolve, so an Ingress you enable without setting a host serves nothing rather than a guessed or wildcard host. TruStacks refuses to open a proposal whose overlay enables the component with the placeholder still in place.

The host is yours to serve: create a DNS record that points it at your ingress controller or OpenShift router, and make sure a TLS certificate matches it. On OpenShift, a host under the cluster’s apps domain (for example *.apps.<cluster-domain>) is covered by the router’s default wildcard DNS and certificate; any other host needs its own record and certificate.

TruStacks does not write native Route objects. If you need a Route-only setting, add your own Route; the agents keep files you added (how they read your repo first).

What TruStacks does not do on OpenShift

  • ImageStreams and BuildConfigs. Image triggers rewrite Deployments and fight ArgoCD. Images are pulled by reference from your registry.
  • DeploymentConfig, which OpenShift deprecated. Workloads are plain Deployments.
  • ProjectRequest. See the namespace note above.
  • OpenShift Pipelines (Tekton). CI is a GitHub Actions workflow today.
  • The internal registry as a declared registry. Use Quay or another registry your cluster can pull from.

Test it as an ordinary user

When you check an install, use a service account or user without cluster-admin. Cluster administrators are granted the anyuid SCC, which hides exactly the failures this page is about.

Troubleshooting

SymptomLikely cause
The runner pod never starts; events mention unable to validate against any security context constraintRunner older than 0.2.15, or openshift=false set on OpenShift
An Application exists but never appears in the ArgoCD UIIt is not in the namespace your ArgoCD instance watches; set gitops_namespace on the cluster
Sync fails with a permission error on the namespaceThe namespace lacks the managed-by label, or the instance’s AppProject doesn’t allow it
Deploy status stays unknownobserver.argocdNamespace doesn’t match where your Applications live

Where to go next

Last updated on