Skip to content

Environment variables

Authoritative list of every environment variable read by Trackarr at runtime — sourced directly from the three application binaries (apps/api, apps/web, apps/tracker) and the shared packages/db connector.

The compose files (docker-compose.prod.yml, docker-compose.loadtest.yml, docker-compose.static.yml) and the Caddyfile read a few operator-side variables on top — those are flagged in the Read by column.

Generate secrets

Anything marked required in the Default column must be set explicitly. The recommended way is openssl rand -hex 32 (32-byte hex string, 64 chars).

Required

The application refuses to start without these. The tracker (Go binary) prints FATAL: <var> is required and exits non-zero; the API throws on boot.

VariableRead byPurpose
DATABASE_URLapi, trackerPostgreSQL DSN. In compose this points at PgBouncer (postgres://…@pgbouncer:6432/…).
REDIS_URLapi, trackerRedis DSN. redis:// or rediss:// for TLS. Pair with REDIS_PASSWORD when needed.
IP_HASH_SECRETapi, trackerDaily-rotated salt for SHA-256 peer-IP fingerprinting. Also keys the UDP connection_id HMAC.
NUXT_SESSION_SECRETapi, webh3 sealed-cookie key (iron-webcrypto). 32+ chars. Fallback for CHANNEL_ENCRYPTION_KEY.
ADMIN_API_KEYapiBearer token for internal admin endpoints. The API returns 503 while it is unset.

Application & runtime

VariableRead byDefaultPurpose
NODE_ENVapi, web, composedevelopmentToggles production-grade defaults (log level, error masking, TLS).
NITRO_PORTapi (compose)4000Nitro listener port inside the API container.
NITRO_HOSTapi (compose)0.0.0.0Bind interface.
TZapi (compose)Europe/ParisContainer timezone — used for bonus_grants.created_at and similar.
LOG_LEVELapiinfo (prod), debugtrace/debug/info/warn/error. Wires into the slog handler.
UPLOADS_DIRapi./data/uploadsFilesystem root for logos, favicons, NFOs. Inside the API container.
API_INTERNAL_URLwebhttp://api:4000Used by Nuxt SSR to fetch from the API across the docker network.
IMAGE_TAGcomposelatestGHCR tag pulled by docker-compose.prod.yml. Pin in production.

Database (PostgreSQL)

DATABASE_URL is the canonical DSN. The split DB_* variables are read only by compose to render the URL and pass POSTGRES_* to the postgres image — the running app never reads them directly.

VariableRead byDefaultPurpose
DATABASE_URLapi, trackerPostgreSQL DSN. Required.
MIGRATIONS_DATABASE_URLapiDATABASE_URLDirect postgres DSN (bypasses PgBouncer) used by drizzle-kit push.
DB_USERcomposetrackerPostgres role; renders into the DSN.
DB_PASSWORDcomposetrackerPostgres password; renders into the DSN.
DB_NAMEcomposetrackarrDatabase name.
DB_PORTcompose5432Listener port (host side).
DB_POOL_MAXapi10pg.Pool size per Nitro worker.
DB_SSLapitrue (prod)true/false. Forces pg.ssl regardless of NODE_ENV.
DB_SSL_REJECT_UNAUTHORIZEDapitrueDisable cert validation when set to false.
DB_SSL_CAapiunsetInline PEM CA bundle (escape newlines as \n).
DB_DEBUGapifalseWhen true, Drizzle logs every query.

Redis

VariableRead byDefaultPurpose
REDIS_URLapi, trackerDSN. Required.
REDIS_PASSWORDapi, trackerunsetWhen set, used in the URL or as an AUTH argument.
REDIS_KEY_PREFIXapi, trackerot:Namespace for every key. Must match across api ⇄ tracker.
REDIS_TLSapifalsetrue forces TLS even when the URL scheme is redis://.
REDIS_TLS_REJECT_UNAUTHORIZEDapitrueDisable cert validation when set to false.
REDIS_TLS_CAapiunsetInline PEM CA bundle for Redis TLS.
REDIS_TLS_CA_FILEapiunsetPath to a CA bundle file (overrides REDIS_TLS_CA).
REDIS_TLS_CERT_FILEapiunsetPath to a client certificate (mTLS).
REDIS_TLS_KEY_FILEapiunsetPath to the matching client key (mTLS).

Tracker (Go binary)

VariableRead byDefaultPurpose
TRACKER_HTTP_PORTtracker8080BEP 3 (HTTP) announce port.
TRACKER_UDP_PORTtracker6969BEP 15 (UDP) announce port.
TRACKER_UDP_ENABLEDtracker, apitrueToggles the UDP listener and UDP-tier inclusion in .torrent downloads. Three processes read this: the tracker (listener), the API stats endpoint (/api/stats/public), and /api/torrents/:hash/download.
TRACKER_DEBUGtrackerfalseVerbose announce + dispatch logging.
TRACKER_PEER_TTLtracker24hHow long a peer's Redis entry survives between announces. Go duration syntax (24h, 90m, 7200s). Values below 15m are clamped — anything shorter would zero the delta computation. Raise it for very forgiving deployments; lower it on huge swarms to reclaim Redis memory.
TRACKER_INTERNAL_URLapihttp://tracker:8080Internal URL the API hits for /api/tracker-health.
TRACKER_HEALTH_URLapiTRACKER_INTERNAL_URLOverride the health-probe URL specifically.
TRUST_PROXYapi, trackerfalseHonour X-Forwarded-For for client IP. Set to true only behind a trusted reverse proxy. The rightmost token is used (the one your proxy appended).
NUXT_PUBLIC_TRACKER_HTTP_URLapi, webhttp://localhost:8080/announceAnnounce URL embedded in .torrent files (runtime).
NUXT_PUBLIC_TRACKER_UDP_URLapi, webunsetUDP tier added when TRACKER_UDP_ENABLED=true and this URL is set.
NUXT_PUBLIC_TRACKER_WS_URLapi, webunsetReserved; WebTorrent (wss) is not implemented yet — don't advertise it.

Tracker tunables aren't env-driven

Announce interval (1800 s), min interval (900 s) and per-response peer cap (50 HTTP / 100 UDP) are hard-coded in apps/tracker/internal/server/response.go. Rebuild the binary if you need to change them.

Security & sessions

VariableRead byDefaultPurpose
NUXT_SESSION_SECRETapi, webRequired. h3 sealed-cookie key. 32+ chars.
IP_HASH_SECRETapi, trackerRequired. Salt for IP fingerprints + UDP connection-id HMAC.
ADMIN_API_KEYapiRequired. Bearer token for internal admin endpoints.
CHANNEL_ENCRYPTION_KEYapifalls back to NUXT_SESSION_SECRETAES key for notification-channel configs at rest (SMTP password, Telegram token, VAPID private key). Generate with openssl rand -hex 32.
TRUST_PROXYapi, trackerfalseSee Tracker — same flag, same semantics, same caveats.

Two-factor auth & WebAuthn

VariableRead byDefaultPurpose
TRACKARR_NAMEapiTrackarrDisplay name shown by authenticator apps as the TOTP issuer and the WebAuthn RP name.
WEBAUTHN_RP_IDapiinferred from OriginPasskey-bound hostname. Pin explicitly behind a load balancer.
WEBAUTHN_ORIGINapiinferred from OriginOrigin allow-list for assertions.

The Force 2FA enforcement scope (off / staff / all) is configured at runtime in /admin/settings and persisted to the settings table, not via env.

Metadata providers

All optional. Without a key, the matching source returns 503 for lookup routes and the upload picker simply hides the column.

VariableRead byDefaultPurpose
TMDB_API_KEYapiunsetTMDb v3 key or v4 Read-Only Access Token (auto-detected). Films + TV.
IGDB_IDapiunsetTwitch Client ID. Required to enable the IGDB games source. Pair with IGDB_SECRET.
IGDB_SECRETapiunsetTwitch Client Secret. Used to mint the IGDB app-access token (cached ~60 days).
GOOGLE_BOOKS_API_KEYapiunsetFallback for the Open Library books source. Open Library itself needs no key.

Notifications

The in-app feed (/notifications + bell) always works. External transports (SMTP, Telegram, Discord, ntfy, Web Push, …) are configured in /admin/notifications and persisted encrypted in notification_channels — no env vars beyond CHANNEL_ENCRYPTION_KEY.

The three webhook URLs below are operator alerts for the API process itself (admin/security events), not user-facing notification channels.

VariableRead byDefaultPurpose
DISCORD_WEBHOOK_URLapiunsetWebhook target for operator alerts emitted by apps/api/utils/alerts.ts.
SLACK_WEBHOOK_URLapiunsetSame, Slack-shaped payload.
SECURITY_WEBHOOK_URLapiunsetSame, generic security webhook (custom integrations).
ENABLE_DEV_ALERTSapifalseWhen NODE_ENV !== 'production', alerts are dropped unless this is true.

Background jobs

VariableRead byDefaultPurpose
BONUS_COLLECTION_INTERVALapi3600000Bonus-collector tick period (ms). Cross-replica SETNX lock; persisted last-tick timestamp ensures exactly one tick across the fleet.
STATS_COLLECTION_INTERVALapi3600000site_stats snapshot period (ms). Same lock model.
BAN_EXPIRY_INTERVALapi300000Ban-expiry cron tick (ms). Sweeps is_banned=true AND banned_until<NOW() and fires account_unbanned. SETNX lock.
REQUEST_AUTO_VALIDATE_INTERVALapi600000Upload-request auto-validate cron tick (ms). Pays the filler when a request sits in filled past the admin-tuned timeout. SETNX lock.

Prometheus metrics

The metrics listener is opt-in and binds its own port — firewall it independently of the public API.

VariableRead byDefaultPurpose
METRICS_ENABLEDapifalseMaster switch (true/1/on/yes).
METRICS_HOSTapi0.0.0.0Bind address.
METRICS_PORTapi9090Bind port.
METRICS_PATHapi/metricsScrape path.
METRICS_AUTH_TOKENapiunsetWhen set, scrapes must present Authorization: Bearer <token>.
METRICS_PEER_CACHE_MSapi30000TTL of the cached Redis SCAN over peers:* used by the gauge.

See Prometheus Metrics reference for the gauge list and example PromQL.

System metadata (/api/admin/system/*)

VariableRead byDefaultPurpose
TRACKARR_REPOapiDim145/opentrackerGit repo (owner/repo) used by /api/admin/system/update to query GitHub for newer tags.
TRACKARR_RUNTIMEapinativeSet to docker inside containers so the admin dashboard renders the right "running in" badge.
APP_VERSIONapiinherited from package.jsonOverride the version reported by /api/admin/system/version.

Static SPA build (build-time)

When building the static frontend (apps/web/Dockerfile.static, docker-compose.static.yml):

VariableDefaultPurpose
NUXT_STATIC_BUILDfalseWhen true, nuxi generate produces the SPA bundle (no SSR).
NUXT_PUBLIC_TRACKER_HTTP_URLunsetBuild-time fallback. The runtime value is fetched from /api/runtime-config on boot, so the same image works across domains.
NUXT_PUBLIC_TRACKER_UDP_URLunsetBuild-time fallback (same mechanism).

Caddy & TLS (compose-only)

VariableRead byDefaultPurpose
DOMAINcaddylocalhostMain hostname; renders into the Caddyfile vhost block.
TRACKER_DOMAINcaddytracker.localhostSubdomain dedicated to announce traffic (also serves /announce).
ACME_EMAILcaddyadmin@example.comLet's Encrypt registration email.

Removed / deprecated

These were read by previous versions and no longer have any effect. If you carry them over from a <= v0.5.x .env, delete them — the new code ignores them silently and that's confusing.

  • TRACKER_HTTP_URL, TRACKER_UDP_URL, TRACKER_WS_URL → replaced by the NUXT_PUBLIC_* variants
  • NUXT_PUBLIC_SITE_NAME, NUXT_PUBLIC_SITE_URL → site title/URL are now in the settings table, edited from /admin/settings
  • NUXT_PUBLIC_ANNOUNCE_URL → use NUXT_PUBLIC_TRACKER_HTTP_URL
  • POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_HOST, POSTGRES_PORT → renamed to DB_* to match the compose surface
  • PORT → was never a real default; the API binds NITRO_PORT (4000)

Example .env

A minimum-viable production .env:

bash
# Domain + TLS
DOMAIN=tracker.example.com
TRACKER_DOMAIN=tracker.example.com
ACME_EMAIL=admin@example.com

# Secrets — generate each with `openssl rand -hex 32`
NUXT_SESSION_SECRET=...
IP_HASH_SECRET=...
ADMIN_API_KEY=...
CHANNEL_ENCRYPTION_KEY=...

# Database
DB_USER=tracker
DB_PASSWORD=...
DB_NAME=trackarr

# Redis
REDIS_PASSWORD=...

# Announce URLs surfaced in .torrent files
NUXT_PUBLIC_TRACKER_HTTP_URL=https://tracker.example.com/announce
NUXT_PUBLIC_TRACKER_UDP_URL=udp://tracker.example.com:6969/announce

# Optional metadata sources
# TMDB_API_KEY=...
# IGDB_ID=...
# IGDB_SECRET=...
# GOOGLE_BOOKS_API_KEY=...

Never commit .env

Add it to .gitignore (already done in the repo). Use .env.example as the template for documentation and CI.

Federation

Inter-instance federation is opt-in — off until the owner enables it in /admin/federation. It introduces no required variables: the instance's Ed25519 private key is encrypted with the same key as notification-channel secrets (CHANNEL_ENCRYPTION_KEY, falling back to NUXT_SESSION_SECRET).

VariableRead byDefaultPurpose
FEDERATION_SYNC_INTERVALapi900000 (15 min)Catalogue-sync cron period in ms. The cron is a no-op while federation is disabled.
TRACKER_FEDERATION_SWARMtrackerfalseMaster switch for swarm cross-announce on the Go tracker. Leave off unless you've deliberately enabled opt-in swarm federation.

See the Federation guide for the full feature, trust model and security notes.

NOTE

Outbound federation calls go through the SSRF-hardened safeFetch, which blocks loopback and private IP ranges. Federating two instances on the same LAN/host therefore needs public hostnames (an explicit host allow-list is not implemented yet).

Released under the MIT License.