Environment Variables
This reference is based on these source files in the cashlytics-ai repository:
.env.exampledocker-compose.selfhost.yml(production self-hosting)docker-compose.yml(local build and development database)docker-compose.demo.yml(demo stack)Dockerfile
Required baseline for self-hosting
Set these before first boot:
POSTGRES_PASSWORD=replace_with_long_random_password
AUTH_SECRET=replace_with_long_random_secret
CRON_SECRET=replace_with_long_random_secret
NEXT_PUBLIC_APP_URL=https://your-domain.tld
AUTH_TRUST_HOST (default true) and SINGLE_USER_MODE (default true) have Compose defaults and can be omitted.
Full variable reference
| Variable | Required | Default | Purpose | Recommended value |
|---|---|---|---|---|
DATABASE_URL | Yes (manual and local setup) | Built in docker-compose.selfhost.yml from POSTGRES_PASSWORD; the .env value is ignored there | PostgreSQL connection string used by the app and the migrations | postgresql://cashlytics:<password>@postgres:5432/cashlytics (Docker), ...@localhost:5432/... (local dev) |
POSTGRES_PASSWORD | Yes (self-host) | None (demo: cashlytics_demo) | Password for the PostgreSQL container user. Ignored by docker-compose.yml, which uses a fixed cashlytics_secret | Long random secret (32+ chars) |
NEXT_PUBLIC_APP_URL | Strongly recommended | http://localhost:3000 in Compose | Base URL for links in emails (fallback for APP_URL). Compiled in at build time, so it cannot change client code of the published image. Authentication uses the request host (AUTH_TRUST_HOST), not this value | Your real HTTPS domain |
NEXT_PUBLIC_DEFAULT_LOCALE | No | de | Fallback language for new users. Build time only, see below | en or de |
NEXT_PUBLIC_DEFAULT_CURRENCY | No | EUR | Fallback currency for new users. Build time only, see below | EUR, USD, GBP, CHF |
AUTH_SECRET | Yes | None (demo: insecure default) | Auth.js secret for tokens/session security | Strong random secret (openssl rand -base64 32) |
AUTH_TRUST_HOST | Strongly recommended | true in the self-host and demo Compose files; not set by docker-compose.yml | Trust the request host behind a proxy or domain. Read by Auth.js itself | true for VPS/reverse proxy |
SINGLE_USER_MODE | No | true in Compose; unset means open registration | true: only the first account can register (registration closes once a user exists). false or unset: open registration | true for personal use |
OPENAI_API_KEY | No | Empty | Enables AI Assistant, Receipt Scanner, and AI reconciliation in CSV Import. Without it these features are hidden; CSV import still works without AI matching | Valid OpenAI API key |
EMAIL_TRANSPORT | No | Empty | Only the value sendmail has an effect (local /usr/sbin/sendmail). Any other value, including smtp, falls back to the SMTP variables | Leave empty for SMTP |
SMTP_HOST | No* | Empty | SMTP server hostname | Provider hostname |
SMTP_PORT | No* | Empty | SMTP server port (must be a number) | 587 (STARTTLS) or 465 (TLS) |
SMTP_USER | No* | Empty | SMTP auth username | SMTP account username |
SMTP_PASS | No* | Empty | SMTP auth password/token | App password or provider token |
SMTP_FROM | No | SMTP_USER; in sendmail mode then noreply@localhost | Sender address for outgoing mails | noreply@your-domain.tld |
APP_URL | No | NEXT_PUBLIC_APP_URL, then http://localhost:3000 | Base URL for links in emails (read at runtime). See "Two meanings of APP_URL" | Same as public app URL |
VAPID_PUBLIC_KEY | No** | Empty | Public key for browser push subscription | npx web-push generate-vapid-keys |
VAPID_PRIVATE_KEY | No** | Empty | Private key to sign push messages | npx web-push generate-vapid-keys |
VAPID_SUBJECT | No** | Empty | Contact URI for VAPID identity | mailto:ops@your-domain.tld |
CRON_SECRET | Yes (self-host stack) | Empty | Bearer token for /api/cron/upcoming-payments and /api/cron/insights. Without it both routes return HTTP 500 | openssl rand -hex 32 |
NOTIFICATION_SCHEDULE | No | 0 8 * * * | Cron schedule (UTC) for the upcoming-payments job. Read only by the cron container, not by the app | Keep default or adjust |
INSIGHTS_SCHEDULE | No | 0 6 * * * | Cron schedule (UTC) for the daily assistant insights job. Read only by the cron container, not by the app | Keep default or adjust |
* SMTP is only active when SMTP_HOST, SMTP_PORT, SMTP_USER, and SMTP_PASS are all set. Otherwise email is off and nobody can reset a password by email.
** Push is only active when VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT are all set; otherwise the app answers HTTP 503 for the VAPID public key.
The self-host Compose file always starts the cron container, which uses CRON_SECRET for both jobs, so treat it as required. It is only optional if you remove the cron container and do not use reminders or insights.
Build-time variables
NEXT_PUBLIC_* values are compiled into the client bundle when the image is built. The published image ghcr.io/aaronjoeldev/cashlytics-ai is built with de and EUR, and setting these variables on the container does not change that (docker-compose.selfhost.yml therefore does not pass them). Users choose language and currency in the settings. To change the defaults, build your own image; see Docker and Build.
Two meanings of APP_URL
- In the app container,
APP_URLis the public base URL used for links in emails (such as the password reset link). - In the cron container of
docker-compose.selfhost.yml,APP_URLis set to the internal addresshttp://cashlytics:3000and is used to call the cron routes. It does not come from your.env. - In
docker-compose.demo.yml,APP_URLis the public URL and is mapped toNEXT_PUBLIC_APP_URL.
Image and demo variables
| Variable | Where | Purpose |
|---|---|---|
PORT | Set by the image | Listening port, 3000 |
HOSTNAME | Set by the image | Bind address, 0.0.0.0 |
NODE_ENV | Set by the image | production |
APP_PORT | docker-compose.demo.yml only | Host port for the demo app (default 3000) |
CHROME_PATH | End-to-end tests only | Path to Chrome if it is not found automatically |
Recommended profiles
Minimal (no AI, no SMTP, no push)
POSTGRES_PASSWORD=...
AUTH_SECRET=...
CRON_SECRET=...
NEXT_PUBLIC_APP_URL=https://app.example.com
AI enabled
OPENAI_API_KEY=sk-...
SMTP enabled
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=mailer@example.com
SMTP_PASS=...
SMTP_FROM=noreply@example.com
APP_URL=https://app.example.com
Push reminders enabled
VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY=...
VAPID_SUBJECT=mailto:ops@example.com
NOTIFICATION_SCHEDULE=0 8 * * *
Validation and safety checks
- Keep
.envout of git. - Use long random values for
POSTGRES_PASSWORD,AUTH_SECRET, andCRON_SECRET. - Keep
NEXT_PUBLIC_APP_URLandAPP_URLaligned with your real domain. - If authentication behaves incorrectly behind a proxy, verify
AUTH_TRUST_HOST=true. - If emails fail, check that all four SMTP variables are set, the port, credentials, and sender domain policies. See Emails.
- For cron problems, see Scheduled Jobs.