Skip to content

Deployment

Lumastra uses PostgreSQL 18 + pgvector, Valkey 9, and the same app image for web and background-worker roles. Choose a deployment method:

MethodBest forGuide
Docker ComposeA single host: home server, NAS, one VMDeploy with Docker
Kubernetes (Helm chart, beta)Multi-replica / high availability. Best-effort support until 1.0Deploy on Kubernetes

Images are published to GHCR:

  • ghcr.io/lumastra/lumastra:stable / :latest: the latest release (intended production tag)
  • ghcr.io/lumastra/lumastra:0.1.0: a specific release
  • ghcr.io/lumastra/lumastra:edge: every commit to main, amd64 only (testing)

A deployment runs separate web and worker containers, PostgreSQL 18 + pgvector, and two Valkey 9 instances: persistent noeviction task/shared coordination and disposable evictable cache. Exactly one lean TypeScript worker runs the existing tasks/plugins and supervises one Rust watcher child in the same container. An optional separate stream proxy handles Live TV. Keep storage services on a private network.

Terminate TLS at Caddy, Traefik, or nginx and forward to port 3000. ORIGIN is required for secure login cookies. Forward auth additionally requires FORWARD_AUTH_ENABLED=true, identity headers and TRUSTED_PROXY_IPS; see Authentication.

The Reverse proxy guide has copy-paste setups for Caddy, nginx, Traefik and Nginx Proxy Manager. The Docker proxy section covers compression, credential-safe logs, and upload limits for all deployment methods.

For two or more web replicas, use the following topology:

Run N web replicas + exactly one worker, sharing PostgreSQL and both Valkeys:

  • Web replicas (WORKER_MODE=disabled) serve HTTP/API/SSE. Scale these freely.
  • Worker replica (start-worker.sh) runs the lean TypeScript task host, existing task/plugin execution, scheduler and supervised Rust watcher child. Run exactly one: it’s the single source of background job orchestration, and it scans the mounted media library. Web replicas and the worker need the same media mounted at the same path, read-only: the worker to scan, the web replicas for direct play and transcoding. Running more than one worker is not supported. If background processing is your bottleneck, give the single worker more CPU/RAM and tune its concurrency. Don’t add worker replicas.
  • Requests for one playback session must reach the same web replica. Per-session HLS transcode state is pinned to the replica that started it. The Kubernetes Helm chart ships a router that does this. Elsewhere, use cookie affinity or source-IP hash on your load balancer, never round-robin.
  • Each replica needs a unique INSTANCE_ID for Valkey coordination, leader election, and the admin cluster view. Leave it unset and Lumastra derives a unique one from the hostname; if you set it explicitly, it must differ per replica.
  • The config volume is shared between web and worker replicas (artwork, trickplay tiles). Mount the media library, read-only and at the same path, on the worker and on every web replica.
  • Database migrations are serialized by a PostgreSQL advisory lock at startup.

See Deploy on Kubernetes for the Helm chart, the per-pod requirements, and the Valkey eviction-policy split.

  • NODE_ENV=production, and ORIGIN=https://your-domain set so secure session cookies work behind your TLS proxy
  • LUMASTRA_MASTER_KEY set to a ≥ 32-byte random value and backed up separately. JWT_SECRET is optional: without it, the signing secret is derived from the master key. Set it only if you want to rotate it on its own, and then use a ≥ 32-byte random value
  • Sign-in configured: a first admin via the /setup wizard (built-in login); for forward auth, FORWARD_AUTH_ENABLED=true and TRUSTED_PROXY_IPS set and admin granted via an ADMIN_GROUPS group in your IdP; or for OIDC, AUTH_OIDC_* set and the callback URI registered
  • Postgres and Valkey on a private network; regular backups (the database and your master key)
  • Valkey eviction policy chosen so task-system keys aren’t evicted (see the Kubernetes guide)
  • Resource limits set (the worker especially: 2–4 GB RAM under load)
  • Logs aggregated from container stdout
  • Compression enabled at the reverse proxy for HTML, CSS, JavaScript and JSON (including /api/v1), but not for text/event-stream or media. See Compression
  • Proxy access logs record paths without query strings. Third-party clients pass tokens and Subsonic passwords as query parameters. See Access logs
  • The proxy’s request-body limit is at least your BODY_SIZE_LIMIT. nginx’s 1 MB default rejects artwork uploads, library upload chunks, and app diagnostics bundles. Behind auth_request, the auth location needs the same limit. See Request body size
  • Running more than one web replica? Session affinity (the Helm chart’s router, or sticky sessions on your load balancer) and a unique INSTANCE_ID per replica. See Scaling to multiple replicas above