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.
- 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.
| Value | Default | Meaning |
|---|---|---|
ui.replicaCount, api.replicaCount | 2 | Web app and API replicas |
worker.replicaCount | 3 | Worker replicas |
worker.concurrency | 12 | Jobs one replica runs at once |
worker.sql.concurrency | 4 | SQL queries per replica against your databases |
worker.isolationLevel | sandbox | Every run isolated (see below) |
scraperEngine.enabled, lsp.enabled | true | Turn off what you do not use |
Install
- Create the database secret
A Secret with a
DATABASE_URLkey. Prefer a Secret to an inline value: a connection string in values ends up inhelm get valuesand in CI logs. - Create the credentials secret
DB_CREDENTIALS_KEYencrypts every secret stored in the database — losing it means losing them, so back it up outside the cluster.SCRAPER_ENGINE_SECRETauthenticates the scraper engine to the API. - Install the chart
Point it at both secrets and turn on ingress with your three hostnames.
- Point DNS and TLS
Create a DNS record for each hostname pointing at the controller’s address (
kubectl get ingress), and setingress.tls.enabled=truewith a certificate for all three, or your cert-manager annotations underingress.annotations.
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.comWorkers 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
sandboxisolation needsSYS_ADMIN,NET_ADMINand 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-workerprofile on every node a worker can run on and setworker.appArmorProfile=waiso-worker(Kubernetes 1.30+). Without it every Python, C and C++ run fails, and the worker logs why at startup. worker.isolationLevel=noneruns scripts as ordinary processes isolated only by the container — use it only when you trust everyone who can write a script.
Upgrade
helm upgrade waiso oci://ghcr.io/talissonjunior16/charts/waiso --reuse-valuesOne 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.