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 live per-PR preview environments are its first consumer, and production reuses it unchanged by flipping two values.

| Mode | postgres.enabled | bootstrap.enabled | Database | |------|--------------------|---------------------|----------| | Preview (default) | true | true | bundled, ephemeral (emptyDir) | | Production | false | false | external, 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-<full-sha>

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.

| Key | Default | Purpose | |-----|---------|---------| | image.repository | ghcr.io/hyperscaleav/omniglass | image to run | | image.tag | latest | pin to sha-<full-sha> for an exact, immutable build | | image.pullPolicy | IfNotPresent | | | server.replicas | 1 | server pod count | | server.resources | small requests/limits | | | service.port | 8080 | ClusterIP service port | | ingress.enabled | false | create an Ingress (on for per-PR previews) | | ingress.className | traefik | ingress controller class | | ingress.host | empty | required when ingress.enabled; the public host | | bootstrap.enabled | true | auto-create an owner (previews) | | bootstrap.username | preview | owner username | | bootstrap.email / bootstrap.displayName | empty / Preview Owner | optional owner fields | | postgres.enabled | true | bundle an ephemeral Postgres | | postgres.image / user / password / database | postgres:18 / omniglass x3 | bundled Postgres settings | | postgres.resources | small requests/limits | bound the throwaway DB | | externalDsn | empty | required when postgres.enabled is false | | server.secureCookies | true | marks session cookies Secure (https-only); set false only for a plaintext-http deployment | | nameOverride | empty | override the chart name in resource names | | fullnameOverride | empty | override resource names outright; default release-named resources keep preview service DNS predictable (e.g. og-pr-42) |

Anything the chart’s values do not cover reaches the server as plain environment (via extraEnv-style values or a Secret). The full server surface renders from the declared environment registry (make gen); behind edge TLS, set OMNIGLASS_SECURE_COOKIES=true and point OMNIGLASS_SECRET_KEY_FILE at a mounted key:

VariableDefaultPurpose
OMNIGLASS_DSN local dev DSN Postgres connection string the Storage Gateway dials; wins over DATABASE_URL when both are set
DATABASE_URL (none) conventional DSN fallback, used when OMNIGLASS_DSN is unset
OMNIGLASS_ADDR :8080 HTTP listen address
OMNIGLASS_SECURE_COOKIES off set true behind TLS to mark the session cookie Secure (https only)
OMNIGLASS_NATS_ADDR 127.0.0.1:4222 listen address (host:port) of the embedded NATS server
OMNIGLASS_NATS_STORE_DIR temp dir JetStream store directory
OMNIGLASS_NATS_URL nats://127.0.0.1:4222 bus URL the node-claim exchange advertises to nodes
OMNIGLASS_DATA_DIR .omniglass local non-Postgres server state (notably the fallback secret key)
OMNIGLASS_SETTINGS_FILE (none) optional operator settings file (JSON or YAML), the layer between code defaults and the DB override
OMNIGLASS_SECRET_KEY (none) base64 32-byte key-encryption key for secrets at rest; wins over the key file
OMNIGLASS_SECRET_KEY_FILE (none) path to the key-encryption key; the intended production source (the generated fallback under the data dir is convenience, not security)

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