Deploy & runGuide

Self-host on Kubernetes

The Helm chart runs the same services as the Compose file — web app, API, workers, image worker, scraper engine and editor service — against a PostgreSQL you already run. Two things are required, the database connection and a credentials secret; everything else has a working default.

10 min readBefore this: Self-host with Docker Compose
What you will learn
  • Create the two secrets the chart needs
  • Install the chart with ingress on three hostnames
  • Size worker replicas and split them into pools
  • Meet the sandbox’s node and AppArmor requirements

What the chart installs

One Deployment per service, each with its own replicas and resources, plus the Services and an optional Ingress. PostgreSQL is not bundled: the chart points at the database you run, and the API applies its migrations on start, like in Compose.

ValueDefaultMeaning
ui.replicaCount, api.replicaCount2Web app and API replicas
worker.replicaCount3Worker replicas
worker.concurrency12Jobs one replica runs at once
worker.sql.concurrency4SQL queries per replica against your databases
worker.isolationLevelsandboxEvery run isolated (see below)
scraperEngine.enabled, lsp.enabledtrueTurn off what you do not use
Every worker replica the chart starts checks in and appears on the Workers page; scale the Deployment and the list grows.

Install

  1. Create the database secret

    A Secret with a DATABASE_URL key. Prefer a Secret to an inline value: a connection string in values ends up in helm get values and in CI logs.

  2. Create the credentials secret

    DB_CREDENTIALS_KEY encrypts every secret stored in the database — losing it means losing them, so back it up outside the cluster. SCRAPER_ENGINE_SECRET authenticates the scraper engine to the API.

  3. Install the chart

    Point it at both secrets and turn on ingress with your three hostnames.

  4. Point DNS and TLS

    Create a DNS record for each hostname pointing at the controller’s address (kubectl get ingress), and set ingress.tls.enabled=true with a certificate for all three, or your cert-manager annotations under ingress.annotations.

Shellterminal
kubectl create secret generic waiso-db \
  --from-literal=DATABASE_URL='postgres://user:pass@db:5432/waiso'
kubectl create secret generic waiso-secrets \
  --from-literal=DB_CREDENTIALS_KEY="$(openssl rand -base64 32)" \
  --from-literal=SCRAPER_ENGINE_SECRET="$(openssl rand -hex 32)"

helm install waiso oci://ghcr.io/talissonjunior16/charts/waiso \
  --set postgres.existingSecret=waiso-db \
  --set credentials.existingSecret=waiso-secrets \
  --set ingress.enabled=true \
  --set ingress.hosts.ui=waiso.example.com \
  --set ingress.hosts.api=api.waiso.example.com \
  --set ingress.hosts.lsp=lsp.waiso.example.com

Workers on Kubernetes

Scale with worker.replicaCount. To keep some replicas for some languages, set worker.languages (for example python,typescript,sql); empty means every language. Compiled C and C++ binaries are cached per replica, or shared across replicas when you give worker.nativeCache.existingClaim a ReadWriteMany claim.

What the sandbox needs

  • sandbox isolation needs SYS_ADMIN, NET_ADMIN and an unconfined seccomp profile, which namespaces enforcing the PodSecurity restricted level refuse. Run the workers in a namespace that allows them.
  • On nodes that enforce AppArmor (Ubuntu, Container-Optimized OS, SUSE), load the waiso-worker profile on every node a worker can run on and set worker.appArmorProfile=waiso-worker (Kubernetes 1.30+). Without it every Python, C and C++ run fails, and the worker logs why at startup.
  • worker.isolationLevel=none runs scripts as ordinary processes isolated only by the container — use it only when you trust everyone who can write a script.

Upgrade

Shellterminal
helm upgrade waiso oci://ghcr.io/talissonjunior16/charts/waiso --reuse-values

One tag, global.imageTag, moves every image together; it defaults to the chart’s version. Migrations run when the new API starts. Back up the database first — there is no downgrade path.