Skip to content

Configuration

Trackarr separates configuration into three layers:

  1. Environment variables — what each container reads at boot (DSNs, secrets, ports, feature flags). The canonical list lives in the Environment variables reference.
  2. Runtime settings — site title, tagline, force-2FA scope, upload constraints, branding assets. Operators edit these from /admin/* pages; they are persisted in the settings table and served live to every container.
  3. Compose / Caddy — what docker-compose.prod.yml and docker/caddy/Caddyfile build on top: image tags, port mappings, vhost routing, TLS certs.

This page describes the layering and the production layout. For every individual env var, jump to the reference.

What goes where

ConcernLayerWhere it lives
Database / Redis DSN, secretsEnv.env → loaded by docker-compose.prod.yml
Announce URL, UDP toggleEnv (runtime)NUXT_PUBLIC_TRACKER_*, TRACKER_UDP_ENABLED
Metadata API keys (TMDb/IGDB/…)EnvTMDB_API_KEY, IGDB_ID, …
Site title, tagline, logo, faviconRuntime/admin/branding + /admin/settings
Upload rules (NFO, regex, …)Runtime/admin/upload-rules
Force-2FA scopeRuntime/admin/settings
Bonus rules + tiersRuntime/admin/bonus-rules
Bonus events (freeleech, …)Runtime/admin/bonus-events
Notification channels (SMTP, Telegram, …)Runtime/admin/notifications
Roles + auto-assignmentRuntime/admin/roles
TLS, domain, ACME emailCompose / CaddyDOMAIN, TRACKER_DOMAIN, ACME_EMAIL + Caddyfile

Why not env-everything?

Anything an admin would reasonably tune without a redeploy lives in the database. The env layer is for things that must be set before the process boots (secrets, DSNs, ports) or that are intrinsically build-time (static-SPA URLs).

Docker compose layout

The production stack ships three independent application containers behind Caddy, plus the standard data layer:

ContainerRole
web (front-ssr)Nuxt 4 SSR — UI shell + page chunks
apiNitro 4 standalone — every /api/*, /uploads/*, SSE notifications
trackerGo 1.25 — BEP 3 HTTP announce on :8080, BEP 15 UDP announce on :6969
postgresPostgreSQL 16
pgbouncerTransaction-mode pool between api/tracker and postgres
redisRedis 7 — peer hashes, sessions, rate-limit windows, pub/sub bus
caddyReverse proxy + automatic Let's Encrypt HTTPS
bash
docker compose -f docker-compose.prod.yml up -d

Static-SPA overlay

When you don't need SSR, swap the web container for a distroless nginx serving the static SPA bundle (~28 MB image, ~7 MB resident memory):

bash
docker compose \
  -f docker-compose.prod.yml \
  -f docker-compose.static.yml \
  --env-file .env \
  up -d

See Local Production — Static SPA variant.

Caddy routing

docker/caddy/Caddyfile splits traffic by path on the main domain and uses a dedicated subdomain for the tracker:

caddyfile
{$DOMAIN} {
    handle /announce*  { reverse_proxy tracker:8080 }
    handle /api/*      { reverse_proxy api:4000 }
    handle /uploads/*  { reverse_proxy api:4000 }
    handle             { reverse_proxy web:3000 }
}

{$TRACKER_DOMAIN} {
    reverse_proxy tracker:8080
}

UDP announces bypass Caddy entirely — Caddy doesn't reverse-proxy UDP, and BEP 15 needs no TLS termination. The tracker container binds 6969/udp directly on the host (see docker-compose.prod.yml).

Caddy is also configured to scrub the passkey query parameter from access logs before they hit disk — the tracker needs the passkey in the URL by BitTorrent protocol, but it must never end up persisted in a log file.

Build-time vs runtime config in apps/web

The static-SPA build patches useRuntimeConfig().public from /api/runtime-config on first paint. That endpoint reads the live tracker URLs from the api container's environment (NUXT_PUBLIC_TRACKER_*), which means a single image is portable across domains — only the api container needs the announce URLs.

Tuning that isn't env-driven

A few constants are hand-tuned in source and need a rebuild:

  • Announce interval / min interval / peer capapps/tracker/internal/server/response.go.
  • PoW difficulty + rate-limit windowsapps/api/utils/{pow,rateLimit}.ts.

If you change them, rebuild the affected container (api or tracker) and redeploy.

Production checklist

Before going live, verify:

  1. Every secret is 32+ chars (openssl rand -hex 32).
  2. IP_HASH_SECRET, NUXT_SESSION_SECRET, ADMIN_API_KEY are all set — the app refuses to start without them.
  3. DOMAIN + TRACKER_DOMAIN resolve to the VPS, port 80/443 inbound is open.
  4. TRUST_PROXY=true (already on in docker-compose.prod.yml) so the rate limiter sees the real client IP through Caddy.
  5. Postgres and Redis ports are not exposed on the host (compose expose: not ports:).
  6. A backup cron is in place — see Backup & Restore.
  7. If you enabled METRICS_ENABLED, firewall the metrics port (default :9090) separately from :80/:443.

For per-feature setup, jump to the right guide from the sidebar: Notifications, Metadata Providers, UDP Tracker, Two-Factor Auth, Panic Mode, and so on.

Released under the MIT License.