Skip to main content

Installation

Set up Lumio using Docker Compose.

Hosted Environments

Lumio runs three environments per app. Production lives on lumio.vision; non-production on zaflun.dev. Use these strings when configuring OAuth callbacks, CORS, env-var defaults and CI deploy targets.

AppProductionProduction PreviewStaging
Webapplumio.visionlumio.web.prod.zaflun.devlumio.web.staging.zaflun.dev
ID Appid.lumio.visionlumio.id.prod.zaflun.devlumio.id.staging.zaflun.dev
Admin Appadmin.lumio.visionlumio.admin.prod.zaflun.devlumio.admin.staging.zaflun.dev
APIapi.lumio.visionlumio.api.prod.zaflun.devlumio.api.staging.zaflun.dev
Docsdocs.lumio.visionlumio.docs.prod.zaflun.devlumio.docs.staging.zaflun.dev
Overlay external URLoverlay.lumio.vision(single per-deployment external host; configurable via NEXT_PUBLIC_OVERLAY_URL on the webapp)(same)

Branch mapping: next → staging · main → production preview · tag 20* → production.

The rest of this guide covers a local development setup using Docker Compose.

Prerequisites

  • Docker and Docker Compose
  • A PostgreSQL 18 instance (or use the provided compose file)
  • Redis 8

Docker Setup

# Start the dev stack (PostgreSQL, TimescaleDB, Redis)
docker compose -f dev-stack/db.docker-compose.yml up -d

# Start the web app
docker compose -f dev-stack/web.docker-compose.yml up -d

Environment Configuration

Copy .env.example to .env and configure:

cp .env.example .env

Key variables:

VariableDescription
DATABASE_URLPostgreSQL connection string
REDIS_URLRedis connection string
AUTH_SECRETNextAuth session secret (ID app)
AUTH_URLID app base URL

NEXT_PUBLIC_WEB_URL

Absolute URL of the apps/web app. Used by apps/id and apps/admin to build absolute links back to the canonical /legal/* pages in the cookie-consent banner footer.

  • Default in code: https://lumio.vision (production)
  • Staging: https://lumio.web.staging.zaflun.dev (set via Cloudflare Pages env settings)
  • Production-preview: https://lumio.web.prod.zaflun.dev
  • Local development: put NEXT_PUBLIC_WEB_URL=http://localhost:4000 in apps/id/.env.local and apps/admin/.env.local so banner links resolve to your local web app instead of production.

apps/web itself does NOT need this variable — it uses relative /legal/* paths because it hosts the pages.

Environment Variable Naming

Every TOML key in apps/api/config/*.toml is overridable via an environment variable using the format LUMIO__<SECTION>__<KEY>double underscore between the prefix and the first segment, and double underscore between every nested segment. Examples:

TOML pathENV var
database.urlLUMIO__DATABASE__URL
auth.token_encryption_keyLUMIO__AUTH__TOKEN_ENCRYPTION_KEY
webhooks.youtube_secretLUMIO__WEBHOOKS__YOUTUBE_SECRET
youtube.innertube_observer.api_key_fallbackLUMIO__YOUTUBE__INNERTUBE_OBSERVER__API_KEY_FALLBACK

The override priority is default.toml < {env}.toml < local.toml < ENV vars, so an env-var always wins.

YouTube Chat Transport

YouTube chat uses InnerTube as the primary transport (zero quota cost). gRPC and REST fallbacks are available but disabled by default:

ENV varPurposeDefault
LUMIO__YOUTUBE__GRPC_FALLBACK_ENABLEDEnable gRPC streamList fallback when InnerTube failsfalse
LUMIO__YOUTUBE__REST_FALLBACK_ENABLEDEnable REST polling fallback when InnerTube and gRPC both failfalse

YouTube InnerTube Settings

The InnerTube chat poller ships with sensible defaults — no configuration is required. For tuning or local debugging, the following ENV overrides map onto the [youtube.innertube_observer] block in apps/api/config/default.toml:

ENV varPurposeDefault
LUMIO__YOUTUBE__INNERTUBE_OBSERVER__API_KEY_FALLBACKCold-boot fallback for the InnerTube API key (auto-rotated at runtime via watch-page scrape)well-known WEB-client key
LUMIO__YOUTUBE__INNERTUBE_OBSERVER__POLL_INTERVAL_FIRST_5MINPolling cadence (s) during the first 5 min of a stream60
LUMIO__YOUTUBE__INNERTUBE_OBSERVER__POLL_INTERVAL_5_TO_30MINPolling cadence (s) between 5 and 30 min120
LUMIO__YOUTUBE__INNERTUBE_OBSERVER__POLL_INTERVAL_AFTER_30MINPolling cadence (s) after 30 min300
LUMIO__YOUTUBE__INNERTUBE_OBSERVER__ON_DEMAND_MIN_INTERVALMin interval (s) for on-demand triggers per account60
LUMIO__YOUTUBE__INNERTUBE_OBSERVER__PARSE_ERROR_THRESHOLDWatchdog trip threshold (0.0–1.0) over a 1 h window0.25
LUMIO__YOUTUBE__INNERTUBE_OBSERVER__CACHE_TTL_SECONDSMember + tier-badge entry TTL in Redis1209600 (14 d)

api_key_fallback is wrapped in a SensitiveString and never appears in logs or tracing output.

Frontend Environment & Logging

apps/web, apps/admin, and apps/id share two env-vars that drive the logger and the Sentry integration. Set both the server-side and the NEXT_PUBLIC_* mirror so the browser bundle picks them up too.

VariableAllowed valuesDefaultPurpose
LUMIO_ENV / NEXT_PUBLIC_LUMIO_ENVdevelopment | staging | productiondevelopmentLogger threshold default + Sentry environment tag
LUMIO_LOG_LEVEL / NEXT_PUBLIC_LUMIO_LOG_LEVELdebug | info | warn | errorper env (debug/info/warn)Override the auto-threshold

Sentry env-vars per app — leave blank locally to disable, fill them in CI / Cloudflare Pages env for staging + production:

VariableWherePurpose
NEXT_PUBLIC_SENTRY_DSNBrowser + serverPublic DSN
SENTRY_DSNServer onlyOptional override; falls back to NEXT_PUBLIC_SENTRY_DSN
SENTRY_ORGBuild (CI)Sentry org slug for source-map upload
SENTRY_PROJECTBuild (CI)Sentry project slug
SENTRY_AUTH_TOKENBuild (CI)Auth token for source-map upload

Each app also has a proxy.ts (Next.js 16's renamed middleware.ts) that prints one structured line per HTTP request to the SSR terminal — including the real client IP (x-real-ip / x-forwarded-for / cf-connecting-ip) with IPv4/IPv6 family classification. See Logging → Per-request access logging for details.

Verify Installation

Visit http://localhost:4000 to access the Lumio dashboard.