Install on Kubernetes (Helm)
Deploy Planeon's control plane on Kubernetes with the official Helm chart:
one values.yaml, a couple of commands, and the same planeon.env
variables as the other install paths — with database migrations wired
into Helm's own install/upgrade lifecycle instead of a manual step.
The Helm chart — and the charts.planeon.io repository it would be published to — is not part of the v1.0.0 release; it's planned for a release after v1.0.0. The supported install path for v1.0.0 is Docker Compose. Everything below (chart contents, values.yaml shape, repository host) describes the intended design so you can plan ahead; treat it as provisional until that later release ships.
Prerequisites
- Kubernetes 1.28 or newer, and the Helm 3 CLI.
- An ingress controller already installed (for example ingress-nginx),
configured for the web console, API, and gateway origins. The gateway
holds a persistent WebSocket connection per desktop session, so raise
your controller's proxy read/send timeouts on the gateway host well past
their defaults — with ingress-nginx,
nginx.ingress.kubernetes.io/proxy-read-timeoutand-proxy-send-timeout(see thevalues.yamlexample below). See Requirements → Network paths for the exact protocols and ports every path needs. - External PostgreSQL 16+ and Redis 7+ reachable from the cluster —
managed services or instances you run yourself. The chart also ships
optional
postgresql/redissubcharts for evaluation; see What the chart deploys below for the production caveat. - A DNS name (or names) resolving to your ingress controller, TLS configured there, and an existing OIDC identity provider with an application already created for Planeon — the same prerequisites as the Docker Compose install, for the same reasons.
- Access to the
planeonDocker Hub organization (or a private mirror of it) for the images the chart references.
If you haven't already, read Requirements for control-plane sizing, browser support, and template OS support before you continue.
What the chart deploys
| Component | Kind | Notes |
|---|---|---|
| api | Deployment | REST API; also serves /healthz and /readyz |
| worker | Deployment | Pool reconciliation and job processing |
| web | Deployment | Node.js web console |
| gateway | Deployment | Browser desktop WebSocket tunnel; needs guacd reachable |
| guacd | Deployment | planeon/guacd:v1.0.0 — Planeon's H.264-enabled build; the stock community image and most distro packages lack H.264 and black-screen modern Windows RDP sessions |
| migrations | Job (Helm hook) | Runs before api/worker/gateway/web roll out, on both install and upgrade — see Install below |
| postgresql | StatefulSet (optional subchart) | Disabled by default; enable only for evaluation — bring your own PostgreSQL 16+ for production |
| redis | StatefulSet (optional subchart) | Disabled by default; same caveat as postgresql |
Add the Helm repository
helm repo add planeon https://charts.planeon.io
helm repo update
values.yaml
The chart itself ships with the v1.0.0 release; the shape below is a
planning reference so you can prepare a values.yaml ahead of time —
treat field names as illustrative until the chart's own documentation and
helm show values planeon/planeon are available. It carries the same
variables as
planeon.env
on the Docker Compose install page; this guide doesn't repeat that
reference here, only the delivery mechanism (Helm values instead of an
env_file) differs.
global:
# Non-secret values, applied to api, worker, web, and gateway.
env:
APP_ENV: production
LOG_LEVEL: info
API_CORS_ALLOWED_ORIGINS: https://vdi.example.com
AUTH_OIDC_ISSUER_URL: https://idp.example.com/application/o/planeon/
AUTH_OIDC_AUDIENCE: planeon
AUTH_OIDC_GROUPS_CLAIM: groups
AUTH_OIDC_ROLES_CLAIM: roles
AUTH_BOOTSTRAP_ADMIN_EMAILS: admin@example.com
NEXT_PUBLIC_API_BASE_URL: https://api.vdi.example.com
NEXT_PUBLIC_OIDC_AUTHORITY: https://idp.example.com/application/o/planeon/
NEXT_PUBLIC_OIDC_CLIENT_ID: planeon
NEXT_PUBLIC_OIDC_SCOPE: "openid profile email groups offline_access"
GATEWAY_PUBLIC_WS_URL: wss://gateway.vdi.example.com/tunnel
# Secret values (DATABASE_URL, REDIS_ADDR, SECRETS_MASTER_KEY) -- create
# this Secret yourself (see below) and reference it here instead of
# putting secrets in values.yaml / source control.
existingSecret: planeon-secrets
api:
image:
repository: planeon/api
tag: "v1.0.0"
replicas: 1
worker:
image:
repository: planeon/worker
tag: "v1.0.0"
replicas: 1
web:
image:
repository: planeon/web
tag: "v1.0.0"
replicas: 1
gateway:
image:
repository: planeon/gateway
tag: "v1.0.0"
replicas: 1
guacd:
image:
repository: planeon/guacd
tag: "v1.0.0"
ingress:
enabled: true
className: nginx
web:
host: vdi.example.com
api:
host: api.vdi.example.com
gateway:
host: gateway.vdi.example.com
# Long-lived WebSocket tunnel for browser desktop sessions -- raise your
# ingress controller's proxy timeouts on this host past their defaults.
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
postgresql:
enabled: false # true only to evaluate -- bring your own PostgreSQL 16+ for production
redis:
enabled: false # true only to evaluate -- bring your own Redis 7+ for production
Create the referenced secret before installing:
kubectl create secret generic planeon-secrets \
--from-literal=DATABASE_URL='postgres://planeon:change-me@postgres.example.internal:5432/planeon?sslmode=disable' \
--from-literal=REDIS_ADDR='redis.example.internal:6379' \
--from-literal=SECRETS_MASTER_KEY="$(openssl rand -base64 32)"
If your Redis requires authentication, also add
--from-literal=REDIS_PASSWORD='...' — like the Docker Compose page's
planeon.env, the example above assumes none.
vdi.example.com, api.vdi.example.com, and gateway.vdi.example.com
above are three separate ingress hosts on one domain — an alternative to
the single-origin, path-routed example on the Docker Compose page. Use
whichever shape fits your DNS; just keep API_CORS_ALLOWED_ORIGINS,
NEXT_PUBLIC_API_BASE_URL, and GATEWAY_PUBLIC_WS_URL in sync with
whatever hosts you choose.
The configuration reference describes every variable carried above — default, whether it's required, whether it's a secret, and what it does.
Install
helm install planeon planeon/planeon -f values.yaml
Unlike the Docker Compose and VM installs, where you run the migration
step yourself, this chart wires migrations into a Helm pre-install/
pre-upgrade hook Job: helm install runs it automatically, before the
api/worker/gateway/web pods roll out. If the hook Job fails, the install
stops there and the release doesn't proceed to create the application
pods — the same ordering guarantee the other install paths document
manually, just automated through the lifecycle hooks Helm already
provides. See Observability → Readiness
semantics for how
api and worker refuse to serve traffic against a schema they don't match,
which is what this ordering protects against either way.
Watch the migration Job directly if you want to follow it:
kubectl get jobs -l app.kubernetes.io/instance=planeon
kubectl logs job/planeon-migrate
The exact job name and labels are set by the chart — adjust to what helm install actually names it once the chart ships.
Upgrading
helm upgrade planeon planeon/planeon -f values.yaml
Same hook: migrations run first, and only once they succeed does Helm roll the new api/worker/gateway/web images out.
Verify
kubectl get pods -l app.kubernetes.io/instance=planeon
kubectl port-forward svc/planeon-api 9090:9090
curl -fsS http://127.0.0.1:9090/readyz
As with the migration Job above, the exact Service name and port are set by the chart — adjust to what the release actually creates once the chart ships.
Once every pod is Running and /readyz returns 200, open the web
console at the ingress host you configured and sign in — the same
first-login bootstrap flow as the Docker Compose
install.
If a pod won't reach Running or /readyz keeps failing, see
Troubleshooting.