Skip to main content

Configuration

Selvara is configured in two places. Deployment wiring — where the database is, what signs the sessions, how long metrics are kept — is environment. Everything an administrator changes while the instance is running — the name on the sidebar, the mail server, what agents are told to do — lives in the database and is edited in the dashboard.

This page lists every environment variable the code actually reads. It was built by reading the source, not the compose file, so it includes a few the shipped docker-compose.yml does not set.

Application​

These are read by the running dashboard.

VariablePurposeRequiredDefaultIf wrong or missing
DATABASE_URLPostgreSQL connection string for the app and the migrations.Yesnonescripts/migrate.mjs prints DATABASE_URL is not set and exits 1, so the container never starts. A wrong host or password produces connection errors on every request and "database":"error" from /api/health.
DATABASE_POOL_MAXMaximum node-pg pool connections.No20Too low starves requests under load; too high exhausts max_connections on the server (the compose file sets it to 200). Note that pool options in the connection string are ignored — this variable is the only knob.
DATABASE_STATEMENT_TIMEOUT_MSPer-statement timeout for the app's pool.No30000Raising it lets one runaway history query pin a connection for minutes. Lowering it makes slow dashboards fail instead of crawl.
REDIS_URLRedis for the live updates and rate-limit counters.In practice yesredis://localhost:6379Unreachable Redis does not take the app down: rate limiting fails open and logs, and /api/health reports "redis":"error" with HTTP 503. Open dashboards stop updating until it is back.
AUTH_SECRETSigns NextAuth session cookies. Also the fallback key for encrypting stored secrets — see below.YesnoneCompose refuses to start without it. Changing it invalidates every session, and without SETTINGS_ENCRYPTION_KEY it also destroys access to stored secrets.
AUTH_URLThe public base URL. NextAuth derives its callback and CSRF endpoints from it; notification and e-mail links are built from it.Yesfalls back to an empty string in link-building codeA mismatch with the address in the browser breaks sign-in. An empty value produces notification links that go nowhere. No trailing slash.
NEXTAUTH_URLThe same public base URL. /api/agent/install.sh and /api/agent/version read only this one, with no fallback to AUTH_URL.YesnoneWith it unset, the generated agent install command and the agent download URL come out with an empty host, so agents cannot update themselves. Always set it to the same value as AUTH_URL.
WEBHOOK_SECRETShared HMAC secret. Agents sign a short-lived JWT with it; the dashboard verifies every agent request against it, and writes it into the config.yaml the installer fetches.YesnoneCompose refuses to start without it. A mismatch makes every agent request 401 and the host looks offline. /api/agent/config answers 500 Server misconfigured when it is unset.
SETTINGS_ENCRYPTION_KEYDerives the AES-256-GCM key for secrets stored in the database.No, but set itfalls back to AUTH_SECRETSee the section below. This one is a foot-gun.
CRON_SECRETBearer token the alert-evaluation endpoint checks.No in code, yes in the shipped compose filenoneSee the section below. Unset means the endpoint is open.
NODE_ENVStandard Node environment. The image sets production.Set by the imageproduction in the imageOutside production the trusted-device cookie is not marked Secure. Do not override it in a deployment.
PORTPort the server binds.Set by the image3000Change it and the compose healthcheck and port mapping, which both name 3000, stop matching.
HOSTNAMEBind address.Set by the image0.0.0.0Binding to loopback inside the container makes it unreachable from the proxy and from the other containers.
NEXT_TELEMETRY_DISABLEDTurns off Next.js telemetry.Set by the image1No operational effect.

Retention and TimescaleDB​

These are read once per container start, by scripts/migrate.mjs, when it configures the hypertable and its policies. They are not read by the running app.

VariablePurposeDefault
METRICS_CHUNK_INTERVALHypertable chunk width for metrics. Retention drops whole chunks, so this is also the granularity at which space comes back.1 day
METRICS_RAW_RETENTIONHow long full-resolution samples are kept.7 days
METRICS_COMPRESS_AFTERAge at which raw chunks are compressed. Typically 10–20x on this kind of data.2 days
METRICS_5M_RETENTIONHow long the metrics_5m rollup is kept. Also bounds the one-time backfill when that rollup is first created.30 days
METRICS_1H_RETENTIONHow long the metrics_1h rollup is kept. This is the longest history any chart can show.400 days
METRICS_MAX_MIGRATE_BYTESSafety rail. If metrics is still a plain table and is larger than this, the conversion to a hypertable is refused rather than attempted, because converting rewrites the whole table and needs that much free disk again.21474836480 (20 GiB)

All of these accept a PostgreSQL interval string, so 12 hours, 14 days and 6 months are all valid.

Changing one and restarting is not always enough. The policies are registered with if_not_exists => true, which leaves an existing policy alone rather than rewriting its interval. Check what is actually installed:

docker exec -i selvara-db psql -U monitoring -d selvara -c \
"SELECT job_id, application_name, config FROM timescaledb_information.jobs
WHERE application_name NOT LIKE 'Telemetry%';"

If a policy still shows the old interval, drop it and let the next start re-register it:

docker exec -i selvara-db psql -U monitoring -d selvara -c \
"SELECT remove_retention_policy('metrics');"
docker compose restart app

Shortening retention deletes data on the policy's next run and that data is gone. Take a dump first — Backup and Restore.

Compose-level​

VariablePurposeDefault
POSTGRES_PASSWORDInterpolated into both the postgres service's POSTGRES_PASSWORD and the app's DATABASE_URL.monitoring_secret — change it

PostgreSQL only applies this when it initialises an empty data directory. Changing it later changes the password the app connects with but not the one the server expects, and the app then cannot reach the database. To rotate it, change it inside PostgreSQL with ALTER ROLE and update .env to match.

CRON_SECRET is optional, and that has consequences​

The alert-evaluation endpoint checks the bearer token only if the variable is set:

const cronSecret = process.env.CRON_SECRET;
if (cronSecret && authHeader !== `Bearer ${cronSecret}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

With CRON_SECRET unset, POST /api/cron/evaluate-alerts — and GET, which is wired to the same handler — is unauthenticated. Anyone who can reach the dashboard can run an alert evaluation cycle, which writes alerts, marks systems offline and sends notifications.

The shipped compose file marks it required for both the app and the cron container, so a Compose deployment cannot start without one. If you run the app some other way, set it. The cron container must be given the same value: it writes the token into a small script rather than into the crontab line, which keeps the secret out of /etc/crontabs and out of the process list.

SETTINGS_ENCRYPTION_KEY falls back to AUTH_SECRET​

Secrets in the database are encrypted with AES-256-GCM using a key derived from SETTINGS_ENCRYPTION_KEY — or, when that is unset, from AUTH_SECRET:

const secret = process.env.SETTINGS_ENCRYPTION_KEY || process.env.AUTH_SECRET;

What that key protects:

  • the SMTP password (app_settings)
  • every agent setting marked as a secret, such as a CrowdSec bouncer key (agent_settings)
  • every user's TOTP secret (users.totp_secret)

None of this fails loudly. Decryption failures are caught and logged, and the caller gets null — so mail silently stops sending, agents silently lose a credential, and users with two-factor authentication enabled can no longer sign in because the server cannot recover the secret to check their codes against.

The trap: if you never set SETTINGS_ENCRYPTION_KEY and later rotate AUTH_SECRET, all of that becomes undecryptable at once. Rotating a session secret is an ordinary thing to want to do, and there is nothing in the act of doing it that suggests it will take the mail server with it.

Set SETTINGS_ENCRYPTION_KEY to its own random value before you store anything, and treat it as permanent. Back it up with the database dump — a dump without the key is a dump with holes in it. If the key is already gone, the recovery is to re-enter the SMTP password and the agent secrets in the dashboard and to have affected users re-enrol their second factor.

What is configured in the dashboard​

These are not environment variables. They live in the database, are edited in the UI, and survive recreating the container.

SettingWhereNotes
SMTP serverSettingsHost, port, encryption (SSL/TLS, STARTTLS or none), user, password, sender address and name, enabled. The password is encrypted. Until SMTP is enabled, POST /api/auth/password/forgot answers 503 Mail is not configured and the login screen does not offer a reset link.
Agent settingsAgents, and per cluster or per systemValues the dashboard hands down to agents, layered cluster first and system on top. Secrets are encrypted at rest and are sent in the clear only to the agent they belong to. See Agent Settings.
Agent rollout modeAgentsWhether updates go out to everything at once or one canary per customer first. See Agent Rollout.
Alert retentionSettings → Data retentionDays after which resolved alerts are deleted, checked once an hour. Default 90; 0 keeps them until someone deletes them. Open alerts are never deleted. Administrators only.
Alert rules, event rules, maintenance windowsSettingsSee Alert Rules, Event Rules, Maintenance Windows.

The split is not arbitrary. Environment holds what the process needs before it can talk to anything — where the database is, what signs a cookie. Everything else is instance data: an administrator changes it at runtime without shell access, it takes effect without a restart, and because it is in the database it is covered by your backup. That is why an administrator can change an alert rule without a redeploy, and why a restored dump comes back with the same SMTP settings it had.

The name and the mark are not in that table. They ship with the application and are the same on every instance; there is nothing to configure and nothing to restore.