Skip to content

Deploying with Helm

The chart at deploy/chart/ is the deploy primitive: one chart serves both disposable per-PR previews and production. It runs the container image; the per-PR preview environments (a later slice) are its first consumer, and production reuses it unchanged by flipping two values.

Modepostgres.enabledbootstrap.enabledDatabase
Preview (default)truetruebundled, ephemeral (emptyDir)
Productionfalsefalseexternal, via externalDsn

The bundled Postgres is deliberately ephemeral: its data is an emptyDir and does not survive a pod restart, which is what you want for a throwaway preview. Production points at a real, managed Postgres.

Preview (bundled Postgres, an auto-created owner):

Terminal window
helm install og-pr-42 deploy/chart -n og-pr-42 --create-namespace \
--set image.tag=sha-<short>

Production (BYO Postgres, no auto-owner):

Terminal window
helm install omniglass deploy/chart -n omniglass --create-namespace \
--set postgres.enabled=false \
--set externalDsn='postgres://user:pass@db:5432/omniglass?sslmode=require' \
--set bootstrap.enabled=false \
--set image.tag=<release>

The server Deployment gates startup with init containers rather than Helm pre-install hooks, because a bundled Postgres in the same release is not up during the pre-install phase:

  1. wait-for-db (only when postgres.enabled) blocks until Postgres answers.
  2. migrate applies the schema (idempotent: dbmate runs each migration once).
  3. bootstrap (only when bootstrap.enabled) creates the first owner and mints its bearer token, printed once to this container’s logs. Idempotent: a restart mints no second token.

Both migrate and bootstrap are safe to re-run on every rollout. At server.replicas > 1 in production, run migrate as a separate one-shot Job instead, to avoid concurrent migrators.

KeyDefaultPurpose
image.repositoryghcr.io/hyperscaleav/omniglassimage to run
image.taglatestpin to sha-<full-sha> for an exact, immutable build
image.pullPolicyIfNotPresent
server.replicas1server pod count
server.resourcessmall requests/limits
service.port8080ClusterIP service port
ingress.enabledfalsecreate an Ingress (on for per-PR previews)
ingress.classNametraefikingress controller class
ingress.hostemptyrequired when ingress.enabled; the public host
bootstrap.enabledtrueauto-create an owner (previews)
bootstrap.usernamepreviewowner username
bootstrap.email / bootstrap.displayNameempty / Preview Owneroptional owner fields
postgres.enabledtruebundle an ephemeral Postgres
postgres.image / user / password / databasepostgres:18 / omniglass x3bundled Postgres settings
postgres.resourcessmall requests/limitsbound the throwaway DB
externalDsnemptyrequired when postgres.enabled is false

By default the chart creates no Ingress: the standing deploy is reached by port-forward (or a cloudflared route dialing the ClusterIP Service directly).

8080/web
kubectl -n omniglass port-forward svc/omniglass 8080:8080
kubectl -n omniglass logs deploy/omniglass -c bootstrap # the one-time token

For per-PR previews, set ingress.enabled=true and an ingress.host. The chart then emits a plain-HTTP Ingress (class traefik) that the in-cluster controller Host-routes; the shared cloudflared tunnel fronts it with one wildcard route and terminates TLS at the edge, so the Ingress carries no tls block.

Terminal window
helm install og-pr-42 deploy/chart -n og-pr-42 --create-namespace \
--set image.tag=sha-<full-sha> \
--set ingress.enabled=true \
--set ingress.host=og-pr-42.preview.hyperscaleav.com \
--set fullnameOverride=omniglass