Skip to main content

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.

Planned for a post-1.0 release

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-timeout and -proxy-send-timeout (see the values.yaml example 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/redis subcharts 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 planeon Docker 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

ComponentKindNotes
apiDeploymentREST API; also serves /healthz and /readyz
workerDeploymentPool reconciliation and job processing
webDeploymentNode.js web console
gatewayDeploymentBrowser desktop WebSocket tunnel; needs guacd reachable
guacdDeploymentplaneon/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
migrationsJob (Helm hook)Runs before api/worker/gateway/web roll out, on both install and upgrade — see Install below
postgresqlStatefulSet (optional subchart)Disabled by default; enable only for evaluation — bring your own PostgreSQL 16+ for production
redisStatefulSet (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.