Yavio

Configuration

Environment variables, secrets, ports, and optional providers

All configuration for the self-hosted platform lives in a single .env file at the repository root. Docker Compose reads this file automatically.

Generating Secrets

Run the setup script to auto-generate all required secrets:

./scripts/setup-env.sh

Or generate them manually:

openssl rand -base64 32

Required Secrets

These must be set before starting the platform. Each should be a unique random string.

VariableUsed ByPurpose
NEXTAUTH_SECRETDashboardEncrypts session cookies
JWT_SECRETIngestion APISigns widget JWTs
API_KEY_HASH_SECRETDashboard + IngestionHMAC key for API key hashing (must match between services)
ENCRYPTION_KEYDashboardAES-256-GCM encryption for OAuth tokens

API_KEY_HASH_SECRET must be identical across the dashboard and ingestion API. If they differ, API keys generated in the dashboard will not authenticate against the ingestion API.

Database Passwords

None of these have a default. They used to fall back to yavio_dev, which is published in the public repository. ./scripts/setup-env.sh generates all eight; if you fill them in by hand, use openssl rand -hex 24.

VariableApplied byRole
POSTGRES_SERVICE_PASSWORDComposePostgreSQL yavio_service — the superuser: owns the tables, bypasses RLS
POSTGRES_API_PASSWORDpnpm migratePostgreSQL yavio_api — what the application actually connects as (not a superuser)
POSTGRES_APP_PASSWORDpnpm migratePostgreSQL yavio_app — RLS enforced
CLICKHOUSE_PASSWORDComposeClickHouse default — unrestricted; runs migrations and manages access
CLICKHOUSE_INGEST_PASSWORDpnpm migrate:clickhouseClickHouse yavio_ingest — INSERT only
CLICKHOUSE_DASHBOARD_PASSWORDpnpm migrate:clickhouseClickHouse yavio_dashboard — SELECT only, row policies
CLICKHOUSE_ERASER_PASSWORDpnpm migrate:clickhouseClickHouse yavio_eraser — ALTER DELETE on the events table only; cannot read what it erases
CLICKHOUSE_ROLLUP_PASSWORDpnpm migrate:clickhouseClickHouse yavio_rollup — ingest's daily statistics job: reads events, writes the anonymous statistics tables, and deletes the raw events of projects set to keep less than 13 months. Unset disables the job: raw events then expire after 13 months with nothing kept, and a project's shorter retention setting is not applied

Compose refuses to start when POSTGRES_SERVICE_PASSWORD, POSTGRES_API_PASSWORD, CLICKHOUSE_PASSWORD or CLICKHOUSE_ERASER_PASSWORD is missing, rather than substituting a value. The dashboard also checks CLICKHOUSE_ERASER_PASSWORD itself when it starts: account, workspace and project deletion run as yavio_eraser, with no fallback to the unrestricted default user. That is deliberate: a startup error takes a minute to fix, a published default is a credential anyone can read on GitHub.

POSTGRES_APP_PASSWORD, CLICKHOUSE_INGEST_PASSWORD and CLICKHOUSE_DASHBOARD_PASSWORD are skipped when unset, so a deployment that shares one password across users keeps working — but each one left unset means that account keeps whatever it was created with. CLICKHOUSE_ROLLUP_PASSWORD is skipped too, but without it ingest's statistics job stays off.

Database Connection URLs

VariableDefaultDescription
DATABASE_URLpostgres://yavio_service:<POSTGRES_SERVICE_PASSWORD>@localhost:5432/yavioPostgreSQL connection string
CLICKHOUSE_URLhttp://default:<CLICKHOUSE_PASSWORD>@localhost:8123ClickHouse HTTP endpoint

These embed a password, so setup-env.sh rewrites them whenever it generates one. Editing a password by hand means editing these too.

In Docker Compose, these are overridden with container hostnames (postgres, clickhouse).

Port Configuration

Override default ports via environment variables:

VariableDefaultService
DASHBOARD_PORT3000Dashboard web UI
INGEST_PORT3001Ingestion API
DOCS_PORT3002Documentation site
POSTGRES_PORT5432PostgreSQL
CLICKHOUSE_HTTP_PORT8123ClickHouse HTTP
CLICKHOUSE_NATIVE_PORT9000ClickHouse native protocol

Example — run the dashboard on port 8080:

DASHBOARD_PORT=8080 docker compose up -d

BIND_ADDRESS (default 0.0.0.0) is the host address the dashboard, ingest and docs ports are published on. The databases are always bound to 127.0.0.1.

Behind a Reverse Proxy

When a reverse proxy on the same host (Caddy, nginx, Traefik) terminates TLS and forwards to the published ports, set:

BIND_ADDRESS=127.0.0.1                 # only the proxy can reach the ports
TRUST_PROXY=172.30.240.1,172.30.241.1  # see below

and start with the proxy override, which pins the Docker networks' subnets:

docker compose -f docker-compose.yml -f docker-compose.proxy.yml up -d

Why TRUST_PROXY. Ingest limits requests that don't authenticate per client address, and logs no addresses at all. Behind a proxy, every request arrives from the proxy, so without TRUST_PROXY all clients share one address. With it, ingest takes the client address from X-Forwarded-For, but only for requests whose direct peer is listed.

Which address to list. Not 127.0.0.1: the proxy connects to a published port, and Docker's port forwarding hands the connection to the container from the gateway of one of the container's networks. Ingest is on two networks and Docker picks one, so list both gateways. docker-compose.proxy.yml pins them to 172.30.240.1 and 172.30.241.1 (override with FRONTEND_SUBNET/FRONTEND_GATEWAY and BACKEND_SUBNET/BACKEND_GATEWAY). Without pinning, the gateway could change whenever the networks are recreated.

TRUST_PROXY takes IP addresses and CIDR ranges only. true, * and named ranges are rejected at startup: if ingest is reachable other than through the proxy, trusting everyone lets any client pick its own address.

Switching an existing deployment to docker-compose.proxy.yml recreates its networks. Run docker compose down first; volumes are kept.

Dashboard Settings

VariableDefaultDescription
NEXTAUTH_URLhttp://localhost:3000Auth callback URL (set to your public domain in production)
APP_URLhttp://localhost:3000Public-facing URL used in emails and links
ERASURE_WORKERonoff only for a non-production copy that shares a production ClickHouse: the dashboard then boots without CLICKHOUSE_ERASER_PASSWORD, deletions stay queued in Postgres and nothing is erased from ClickHouse. A dev dashboard seeded from a production Postgres dump carries the production workspace and project ids, so without this a deletion there would erase the production tenant's analytics. Never set it in production. Any other value stops the boot

OAuth Providers (Optional)

Enable social login by setting provider credentials. Omit to disable.

# GitHub
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...

# Google
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

Email / SMTP (Optional)

Enable email features (verification, password reset, invitations) by configuring SMTP. If unset, email features are disabled.

SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=resend
SMTP_PASSWORD=re_...
SMTP_FROM=[email protected]

Production Overrides

For production deployments, use the production compose overlay which adds resource limits and log rotation:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Or via the CLI:

npx @yavio/cli up --prod

Resource Limits (Production)

ServiceCPUMemory
PostgreSQL22 GB
ClickHouse24 GB
Ingestion API11 GB
Dashboard11 GB
Docs0.5512 MB

On this page