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.shOr generate them manually:
openssl rand -base64 32Required Secrets
These must be set before starting the platform. Each should be a unique random string.
| Variable | Used By | Purpose |
|---|---|---|
NEXTAUTH_SECRET | Dashboard | Encrypts session cookies |
JWT_SECRET | Ingestion API | Signs widget JWTs |
API_KEY_HASH_SECRET | Dashboard + Ingestion | HMAC key for API key hashing (must match between services) |
ENCRYPTION_KEY | Dashboard | AES-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.
| Variable | Applied by | Role |
|---|---|---|
POSTGRES_SERVICE_PASSWORD | Compose | PostgreSQL yavio_service — the superuser: owns the tables, bypasses RLS |
POSTGRES_API_PASSWORD | pnpm migrate | PostgreSQL yavio_api — what the application actually connects as (not a superuser) |
POSTGRES_APP_PASSWORD | pnpm migrate | PostgreSQL yavio_app — RLS enforced |
CLICKHOUSE_PASSWORD | Compose | ClickHouse default — unrestricted; runs migrations and manages access |
CLICKHOUSE_INGEST_PASSWORD | pnpm migrate:clickhouse | ClickHouse yavio_ingest — INSERT only |
CLICKHOUSE_DASHBOARD_PASSWORD | pnpm migrate:clickhouse | ClickHouse yavio_dashboard — SELECT only, row policies |
CLICKHOUSE_ERASER_PASSWORD | pnpm migrate:clickhouse | ClickHouse yavio_eraser — ALTER DELETE on the events table only; cannot read what it erases |
CLICKHOUSE_ROLLUP_PASSWORD | pnpm migrate:clickhouse | ClickHouse 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
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | postgres://yavio_service:<POSTGRES_SERVICE_PASSWORD>@localhost:5432/yavio | PostgreSQL connection string |
CLICKHOUSE_URL | http://default:<CLICKHOUSE_PASSWORD>@localhost:8123 | ClickHouse 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:
| Variable | Default | Service |
|---|---|---|
DASHBOARD_PORT | 3000 | Dashboard web UI |
INGEST_PORT | 3001 | Ingestion API |
DOCS_PORT | 3002 | Documentation site |
POSTGRES_PORT | 5432 | PostgreSQL |
CLICKHOUSE_HTTP_PORT | 8123 | ClickHouse HTTP |
CLICKHOUSE_NATIVE_PORT | 9000 | ClickHouse native protocol |
Example — run the dashboard on port 8080:
DASHBOARD_PORT=8080 docker compose up -dBIND_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 belowand start with the proxy override, which pins the Docker networks' subnets:
docker compose -f docker-compose.yml -f docker-compose.proxy.yml up -dWhy 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
| Variable | Default | Description |
|---|---|---|
NEXTAUTH_URL | http://localhost:3000 | Auth callback URL (set to your public domain in production) |
APP_URL | http://localhost:3000 | Public-facing URL used in emails and links |
ERASURE_WORKER | on | off 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 -dOr via the CLI:
npx @yavio/cli up --prodResource Limits (Production)
| Service | CPU | Memory |
|---|---|---|
| PostgreSQL | 2 | 2 GB |
| ClickHouse | 2 | 4 GB |
| Ingestion API | 1 | 1 GB |
| Dashboard | 1 | 1 GB |
| Docs | 0.5 | 512 MB |