Appearance
Environment Variables
All variables live in a single .env file at the repo root. The VITE_* ones are inlined into the frontend bundle at build time; the rest are read at runtime by the API and worker.
Frontend (VITE_*)
Read by Vite at build time and inlined into the JavaScript bundle. They are publicly visible.
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_APP_VERSION | No | 0.0.0 | Version string displayed in the UI |
The client reaches the API over a relative /api path (same-origin, reverse-proxied), so no API-URL variable is needed.
Backend
Read at runtime by both services/api and services/worker. Never exposed to the browser.
Database
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | Yes | — | PostgreSQL connection string |
POSTGRES_USER | Yes | tt | PostgreSQL username (Docker Compose) |
POSTGRES_PASSWORD | Yes | — | PostgreSQL password — change from default in production |
POSTGRES_DB | Yes | tt | PostgreSQL database name |
Redis
| Variable | Required | Default | Description |
|---|---|---|---|
REDIS_URL | Yes | redis://localhost:6379 | Redis connection string for BullMQ |
Authentication
| Variable | Required | Default | Description |
|---|---|---|---|
BETTER_AUTH_SECRET | Yes | — | Session signing secret. Generate with openssl rand -base64 32. Minimum 32 characters. |
BETTER_AUTH_URL | Yes | http://localhost:3000 | Public URL of the API, used for OAuth redirect URIs |
Google OAuth
| Variable | Required | Description |
|---|---|---|
GOOGLE_CLIENT_ID | For login | OAuth client ID from Google Cloud Console |
GOOGLE_CLIENT_SECRET | For login | OAuth client secret |
Web / CORS / email links
| Variable | Required | Default | Description |
|---|---|---|---|
CORS_ORIGINS | Yes | — | Comma-separated list of allowed frontend origins. Include capacitor://localhost (iOS) and https://localhost (Android) for the native mobile app, which calls the API from its own origin |
GOOGLE_IOS_CLIENT_ID | No | — | OAuth client ID of the iOS app, accepted as audience of Google ID tokens the native app signs in with. Unset: no Google button on iOS |
Native push (mobile app)
Each channel is optional and independent; an unset channel delivers nothing to devices of that platform.
| Variable | Required | Default | Description |
|---|---|---|---|
FCM_SERVICE_ACCOUNT | No | — | Android: base64 of the Firebase service-account JSON (FCM HTTP v1) |
APNS_KEY_ID | No | — | iOS: the APNs key ID from the Apple developer portal |
APNS_TEAM_ID | No | — | iOS: the Apple team ID |
APNS_KEY_P8 | No | — | iOS: base64 of the AuthKey_<id>.p8 file |
APNS_BUNDLE_ID | No | ovh.tim.app | iOS: the app's bundle ID (apns-topic) |
APNS_ENVIRONMENT | No | production | sandbox for Xcode debug builds, production for TestFlight / App Store |
APP_URL | No | http://localhost:5173 | Frontend URL used in transactional email links |
File Storage
| Variable | Required | Default | Description |
|---|---|---|---|
RUSTFS_ENDPOINT | Yes | http://localhost:9000 | S3-compatible storage endpoint |
RUSTFS_BUCKET | Yes | tt-invoices | Bucket for invoice files |
RUSTFS_ACCESS_KEY | Yes | minioadmin | Storage access key |
RUSTFS_SECRET_KEY | Yes | minioadmin | Storage secret key |
AI / OCR
| Variable | Required | Description |
|---|---|---|
OPENROUTER_KEY | Worker | OpenRouter API key for AI invoice extraction. Required by the worker (it refuses to boot without it). |
EXTRACTION_MODEL | No | OpenRouter model slug for invoice extraction. Default: google/gemini-2.5-flash. |
EXTRACTION_FALLBACK_MODEL | No | Fallback model used when the primary model's providers are unavailable. Default: openai/gpt-5-mini. |
Secret encryption
| Variable | Required | Description |
|---|---|---|
INTEGRATION_ENCRYPTION_KEY | For email sync / integrations | AES-256-GCM key (base64, 32 bytes) encrypting accounting-integration credentials and IMAP email-sync passwords at rest. Must match between API and worker. Generate with openssl rand -base64 32. |
Transactional email (all optional)
Leave SMTP_HOST empty to disable email — user creation still works, mail just isn't sent.
| Variable | Default | Description |
|---|---|---|
SMTP_HOST | — | SMTP server host |
SMTP_PORT | 587 | SMTP server port |
SMTP_USER | — | SMTP username |
SMTP_PASS | — | SMTP password |
SMTP_FROM | noreply@tt-tracker.app | From address for outgoing mail |
Web push notifications (all optional)
Leave the VAPID keys empty to disable push delivery — the in-app notification center keeps working. Generate a key pair with npx web-push generate-vapid-keys.
| Variable | Default | Description |
|---|---|---|
VAPID_PUBLIC_KEY | — | VAPID public key, served to browsers subscribing to push |
VAPID_PRIVATE_KEY | — | VAPID private key used to sign push deliveries |
VAPID_SUBJECT | mailto:noreply@tt-tracker.app | mailto: or https: contact for the push service operator |
API Server
| Variable | Required | Default | Description |
|---|---|---|---|
PORT | No | 3000 | Port the NestJS API listens on |
Observability (all optional)
| Variable | Description |
|---|---|
SENTRY_DSN | Sentry DSN for API + worker error tracking |
SENTRY_DSN_FRONTEND | Sentry DSN for frontend error tracking (served to the browser via /config) |
UMAMI_WEBSITE_ID | Umami analytics website id (served to the browser via /config) |
Database backup variables (BACKUP_*, BACKUP2_*) are documented in ops/backup/README.md and the Docker Compose reference.