Self-hosting
- Prerequisites
- Messaging and runner connectivity
- Upgrading to chart 0.8.1
- Upgrading to chart 0.8.0
- Upgrading from SpiceDB to Casbin
- Plugins
- OpenBao after a restart
- Optional OIDC issuer
- Platform Orchestrator Helm Charts
- Charts
- Installation in the KinD cluster
- Installation in a random cluster with default values
- Releasing the OCI chart
- Organization and user management
- Smoke Tests
On this page
- Prerequisites
- Messaging and runner connectivity
- Upgrading to chart 0.8.1
- Upgrading to chart 0.8.0
- Upgrading from SpiceDB to Casbin
- Plugins
- OpenBao after a restart
- Optional OIDC issuer
- Platform Orchestrator Helm Charts
- Charts
- Installation in the KinD cluster
- Installation in a random cluster with default values
- Releasing the OCI chart
- Organization and user management
- Smoke Tests
Install and operate the Platform Orchestrator in your own infrastructure.
The Platform Orchestrator is currently offered as self-hosted software. You manage the installation and its infrastructure in your own data center (“on premise”), a shared hosting facility, or your account with a public cloud provider.
The installation is based on Helm charts bundled via Helmfile .
Since you also manage the data storage, the self-hosted setup enables data sovereignty.
An air-gapped runner design without internet ingress or egress is included for runner types that do not depend on a public OIDC issuer. It is not production-qualified; refer to the limitations below.
Prerequisites
Infrastructure:
- Kubernetes cluster
- PostgreSQL database (v17+)
- Or use the optional in-cluster PostgreSQL as part of the installation package (not recommended for production use)
- NATS JetStream, installed by the chart or provided as an internal service
- DNS and TLS certificates for the domains of the Orchestrator console and API endpoint
- Optional: a dedicated public DNS name and publicly trusted TLS certificate for the per-install OIDC issuer when using AWS or Google Cloud identity federation
For planned air-gapped environments, in addition to the above:
- Internal container registry (OCI-compatible)
- Ability to mirror Terraform providers
Air-gapped installations cannot use runner types that require AWS or Google Cloud to retrieve public OIDC discovery and signing-key documents. Other runner types remain available.
The air-gap relay has not been qualified with a physical diode. Do not treat that path as production-validated until it has passed acceptance tests with the selected device.
Messaging and runner connectivity
The installation uses JetStream internally for durable service events, runner commands, bundles, results, and encrypted logs. Do not expose the bundled NATS listener to runner networks.
The runner chart and ECS integration use the HTTPS runner gateway. Remote
networks need only outbound TCP 443 to
https://<api-host>/runner-gateway. Runner commands are buffered centrally
while an edge is disconnected. Enable the runner-side persistent outbox in
edge mode to buffer results and encrypted logs; multi-node clusters require
ReadWriteMany storage.
Set data-plane.config.RUNNER_GATEWAY_URL to that externally reachable HTTPS
URL. The separate RUNNER_GATEWAY_INTERNAL_URL defaults to the gateway’s
cluster-local Service and is used only for Kubernetes Jobs created inside the
Orchestrator cluster. This distinction matters for ECS and any other runner
outside cluster DNS.
See runner gateway architecture for authentication, scaling, and air-gap topology.
Tools:
Upgrading to chart 0.8.1
Chart 0.8.1
defaults pair Control Plane 3.1.1, Data Plane 4.0.1, IAM 2.5.1, and Console
1.4.2. CLI and Terraform/OpenTofu provider 2.1.1 are matching dependency
patches; their 2.1.0 API and state contracts remain compatible. Runner remains
at 3.1.0. Read the chart release notes. Verify the signed source tag and the
digest of the chart package you download. Do not use the incomplete Console
1.4.1 publication as an upgrade target.
Chart 0.8.1 also pairs the Keycloak server and Operator at 26.8.1. Install the
paired Operator and its CRDs before upgrading the server. Existing v2alpha1
resources remain served alongside the new v2beta1 definitions. If upgrading
from Keycloak 26.0.x, back up the Keycloak database and configuration and test
restoration with the original server image. Stop the old server before the new
one migrates the database. Helm rollback alone cannot reverse that migration:
to recover, stop the new server, restore the original database snapshot and
configuration, then start the old image. Follow the
Keycloak 26.8.1 upgrade guide
and the chart’s release notes. The chart’s optional Gateway
external traffic policy setting keeps its current default; if selecting
Local, verify real HTTP and TLS reachability after proxy rescheduling.
Back up Orchestrator databases and configuration too. The application patches do not add a database migration, but database Down migrations are not a recovery procedure. Use a schema-compatible application revision or forward repair. The Runner and plugin runtime are unchanged. External OpenBao is maintained separately and is not upgraded by this chart.
Upgrading to chart 0.8.0
Chart 0.8.0 coordinates CP 3.1.0, DP 4.0.0, IAM 2.5.0, Runner 3.1.0 and Console 1.4.0. Use CLI and Terraform/OpenTofu provider 2.1.0 with it. Check provider availability and read the release notes before upgrading an existing installation.
Module Version Management is part of the Orchestrator.
Use the compatible component versions listed in the release notes,
including the Runner Job image and the standard chart’s API routes. Existing
opaque Module versions migrate as generation v0; new versions use complete immutable
definitions, canonical SemVer and explicit Proposed-to-Default promotion.
Read Module upgrade, state migration and recovery before upgrading components that write Module data. Back up databases, encryption and key storage, installation configuration and Terraform/OpenTofu state. The new authoring contract requires changes to legacy automation even though existing deployment identities remain.
Upgrading from SpiceDB to Casbin
Chart 0.4.3 automatically migrates a supported SpiceDB-based installation to
Casbin RBAC during a normal Helm upgrade. The qualified source is IAM v2.0.1
at database schema 29. Upgrade older installations to that version first.
Before upgrading, back up the IAM PostgreSQL database, confirm that IAM can
reach the control plane, and retain the old SpiceDB database during the
rollback window. The chart uses a Recreate rollout, so all old IAM Pods stop
before the database migration starts and IAM is briefly unavailable.
The first new IAM replica serializes the migration with a PostgreSQL advisory
lock, reconstructs the organization, project, and environment hierarchy, and
verifies the result before becoming ready. Interrupted upgrades resume on the
next restart; failed verification prevents IAM from serving requests. Follow
the complete SpiceDB-to-Casbin upgrade and rollback procedure
before deploying chart 0.4.3.
Plugins
The plugin foundation is available with chart 0.8.0 and the component versions
listed above. See the changelog
for the compatible set and breaking changes. Do not mix these instructions with
an older installation.
Releases with the plugin host can run first-party Stellwerk plugins in their own workloads. The plugin host is off by default, and a Core-only installation needs no plugin, private image, or trust material. Read Core-only installations for the behavior changes that apply to every installation, and Plugin host architecture before you install a plugin.
IAM decides who operates the plugins of the installation. Name those users in
iam.config.PLUGIN_MANAGER_WRITERS and iam.config.PLUGIN_MANAGER_READERS. Both
are empty by default, so nobody can manage plugins until you set them. The
plugin workloads need Kubernetes 1.30 or newer. See
Install and operate native plugins.
OpenBao after a restart
The Orchestrator keeps its secrets in OpenBao (or another Vault-compatible
store). The Helmfile installs OpenBao as a separate release in the openbao
namespace, with Raft storage, and its setup hook initializes and unseals it
once, at deploy time. Nothing unseals it afterwards.
OpenBao starts sealed. After a node or container runtime restart, every OpenBao Pod therefore comes back sealed. While it is sealed, the Control Plane cannot use it and restarts in a loop until you unseal OpenBao.
To recover, unseal every OpenBao Pod with the unseal key of your installation:
kubectl -n openbao exec -it openbao-0 -- bao operator unseal
Repeat the command for each Pod of the StatefulSet. The Control Plane starts on its next restart.
For production, configure OpenBao auto-unseal so that a restarted Pod unseals itself, and keep the recovery keys outside the cluster.
Optional OIDC issuer
The data plane includes an optional OIDC issuer for authenticating runners to AWS and Google Cloud without long-lived cloud credentials. It is disabled unless data-plane.config.OIDC_ISSUER_URL is set.
To enable it, configure a dedicated hostname under global.gatewayApi.oidc.hostname, terminate it with publicly trusted TLS, and set data-plane.config.OIDC_ISSUER_URL to the exact public URL, for example https://oidc.orchestrator.example.com. The cloud identity service must be able to retrieve these endpoints:
/.well-known/openid-configuration/.well-known/jwks
The issuer belongs to this installation. Stellwerk does not operate a shared OIDC issuer.
Platform Orchestrator Helm Charts
Helm charts to install and operate Platform Orchestrator in Kubernetes.
Chart 0.4.3 delivers configurable Casbin RBAC with console-based role
management and automatically upgrades supported SpiceDB-based installations.
It retains the HTTPS runner gateway and internal NATS JetStream architecture
introduced in 0.3.0, with an application-account-safe NATS bootstrap probe.
Charts
All releases are managed via Helmfile . The main chart
is platform-orchestrator; the others are prerequisites (operators, CRDs, and
infrastructure) that it depends on.
| Release | Chart | Type | Description |
|---|---|---|---|
envoy-gateway | oci://docker.io/envoyproxy/gateway-helm | External | Envoy Gateway controller |
gatewayclass | charts/gatewayclass | Local | GatewayClass resource for Envoy Gateway |
cnpg | cnpg/cloudnative-pg | External | CloudNativePG operator for PostgreSQL |
cert-manager | oci://quay.io/jetstack/charts/cert-manager | External | TLS certificate management |
keycloak-operator | charts/keycloak-operator | Local | Keycloak operator |
openbao | openbao/openbao | External | OpenBao (Vault-compatible secret store) |
cluster-issuer | charts/cluster-issuer | Local | cert-manager ClusterIssuer resource |
opentelemetry-operator | opentelemetry-operator/opentelemetry-operator | External | OpenTelemetry operator |
opentelemetry | charts/opentelemetry | Local | OpenTelemetry Collector configuration |
platform-orchestrator | charts/platform-orchestrator | Local | Platform Orchestrator and its dependencies (values reference ) |
Each release can be toggled on/off via the helmfile environment state values (e.g., installEnvoyGateway, installPostgres, etc.).
Installation in the KinD cluster
Run:
make create-kind
make deploy-local
make add-certs-macos # for MacOS, for other OSes see how to trust self-hosted certs there
OR (which is the same):
make setup-local
Add your domains to /etc/hosts, e.g.:
127.0.0.1 api.example.com
127.0.0.1 console.example.com
127.0.0.1 keycloak.example.com
(The domains are configured in state-values.yaml in platformOrchestratorValues.global.[api|console|keycloak].hostname)
Installation in a random cluster with default values
Important: you need to switch your kubectl context to the correct cluster before running this command!
Configure image registry
The default chart values use the public images under ghcr.io/stellwerk-labs,
so no registry credentials are required. To use a private mirror, override the
component image repositories and configure global.imagePullSecrets.
Install Platform Orchestrator
make deploy
The chart-generated NATS token is intended for local bootstrap. Before a production rollout, provision per-service and per-runner subject-scoped NATS credentials and establish rotation and revocation procedures.
Chart 0.4.3 automatically upgrades IAM schema 29 from SpiceDB to Casbin during
the first replacement Pod’s startup. Read the
SpiceDB-to-Casbin upgrade notes for the
required backup, short IAM outage, verification, and rollback procedure.
Releasing the OCI chart
Chart publication is deliberately manual. Updating a component image does not
publish a chart by itself. When a coherent set of image tags is ready, update
the default image tags in charts/platform-orchestrator/values.yaml, increment
the chart version in charts/platform-orchestrator/Chart.yaml, and commit the
change. Create and push a signed v<chart-version> tag on that commit, then run
the Release Chart workflow from that tag. The workflow validates the tag and
chart version, lints and packages the chart, pushes it to
oci://ghcr.io/stellwerk-labs/charts, and creates the matching GitHub release.
Organization and user management
Create organizations with admin API
Get superuser token:
export SUPERUSER_TOKEN=$(kubectl get secret platform-orchestrator-secrets -n platform-orchestrator -o jsonpath='{.data.superUserToken}' | base64 --decode)
List all organizations (take the API URL from platformOrchestratorValues.global.api.hostname in state-values.yaml):
export API_URL=https://api.example.com
curl -k -H "Authorization: Bearer ${SUPERUSER_TOKEN}" ${API_URL}/admin/orgs
Create new organization:
curl -k -XPOST \
-H "Content-Type: application/json" -H "Authorization: Bearer ${SUPERUSER_TOKEN}" \
-d '{"id":"my-org"}' \
${API_URL}/admin/orgs
User management with Keycloak
- Get Keycloak admin credentials:
make print-keycloak-admin
- Login to your Keycloak instance (see the hostname in state-values.yaml,
platformOrchestratorValues.global.keycloak.hostname), e.g. https://keycloak.example.com/ - Switch realm from
mastertoplatform-orchestrator(the realm is created with the helm installation, you can customize its name with helm values) - Add a new user under Users and set a password
- Go to Console (see the hostname in state-values.yaml,
platformOrchestratorValues.global.gatewayApi.frontend.hostname), e.g. https://console.example.com/ and click on SSO Login - Put in your org name
Smoke Tests
The test/ directory contains a smoke test that provisions a minimal Platform Orchestrator setup (project, environment, runner, module) and triggers a deployment to validate the full pipeline end-to-end.
Prerequisites
terraforminstalledoctlinstalled and authenticatedkubectlconfigured and pointing at the target cluster (used to pull cluster credentials)- Platform Orchestrator deployed and accessible
Setup
cd test/
terraform init
terraform apply
Note: All variable defaults are pre-configured for the local KinD cluster set up via
make setup-local. No variable overrides are needed when running against that cluster.
This provisions:
- A Platform Orchestrator project, environment, and environment type
- A Kubernetes runner registered with Platform Orchestrator
- A test resource type, module, and module rule
Run the test
cd test/
bash run-test.sh
This deploys manifest.yaml using octl, which triggers the runner to execute the test module and verify the pipeline is working end-to-end.
Teardown
cd test/
terraform destroy