Environment variables
This page is generated from the .env.example files in the repository, so it lists every variable they document. set means the example sets the variable; optional means it is commented out, and the value shown is its default or an example.
Install settings
Section titled “Install settings”These live in nautelle/.env, next to the compose file. install.sh writes the file and fills both passwords; a re-run keeps what is there. Source: deploy/compose/.env.example.
| Variable | Value in the example | Description | |
|---|---|---|---|
NAUTELLE_URL |
http://localhost:8000 |
set | The address people open, without a trailing slash. API_URL, WEB_URL, APP_URL and PUBLIC_URL derive from it; set one here to override it. PUBLIC_URL= (empty) keeps the GitHub App’s webhook off: install.sh writes it for an http:// URL, which GitHub cannot reach. |
COMPOSE_PROFILES |
local |
set | The exposure mode: local, tunnel or tls (exactly one). |
NAUTELLE_BINDNAUTELLE_PORT |
127.0.0.18000 |
set | local: where Caddy listens on the host. 0.0.0.0 opens it to the network. |
TUNNEL_TOKEN |
empty | set | tunnel: the token of a remote-managed Cloudflare tunnel whose public hostname points at http://proxy:80. NAUTELLE_URL is that hostname’s https URL. |
NAUTELLE_DOMAIN |
empty | set | tls: the domain Caddy gets a certificate for. Its DNS points at this host and ports 80 and 443 are open. NAUTELLE_URL is https://<domain>. |
NAUTELLE_VERSION |
latest |
set | Image tag: latest, or a release such as 0.15.0. |
NAUTELLE_REGISTRY |
empty | set | Where the images come from: empty for ghcr.io/andreybegma, or docker.io/abegma for the same images on Docker Hub (the same digests and signatures). |
APP_ENV |
production |
set | production marks cookies Secure. install.sh writes development for an http:// NAUTELLE_URL, where a browser would drop them. |
POSTGRES_PASSWORDREDIS_PASSWORD |
empty empty |
set | Database and Redis passwords. Required: there is no default. |
In a compose install these go in nautelle/api.env, one KEY=value per line, and docker compose up -d applies them. The install already sets the URLs, APP_ENV, TRUST_PROXY, DATABASE_URL and REDIS_URL; the variables only the test suite reads matter to contributors alone. Source: apps/api/.env.example.
| Variable | Value in the example | Description | |
|---|---|---|---|
PORT |
8180 |
set | — |
APP_ENVAPI_URLWEB_URL |
developmenthttp://localhost:8180http://localhost:3517 |
set | development and test send the session and CSRF cookies over plain http; any other value, or none, marks them Secure. Set production when deployed. |
TRUST_PROXY |
loopback |
set | Express trust proxy: whose X-Forwarded-For to believe for the client IP (per-IP login throttle, and the audited IP of /terminal and /runner sockets). loopback trusts a proxy on the same host — the web app’s /api rewrite. Behind another reverse proxy, set its address or hop count. true trusts every hop and a client can forge X-Forwarded-For: the API refuses to boot with it when APP_ENV=production. Count the proxies between the client and the API, then pick the value for your topology: dev, or single host (browser -> Next /api rewrite -> API) loopback nginx in front of Next (browser -> nginx -> Next -> API) 2 staging (Cloudflare -> cloudflared -> Caddy -> API) uniquelocal Never true in production. |
DATABASE_URL |
"postgresql://postgres:@localhost:5421/agentdock?schema=public" |
set | The database and Redis URLs ship without a password: bun run db:setup writes the passwords it generates into docker/.env into every URL that has none. |
TEST_DATABASE_URL |
"postgresql://postgres:@localhost:5421/agentdock_test?schema=public" |
set | Database the e2e tests (bun run test) wipe and migrate on every run. Its name must end in test; it is created if it does not exist. It is the template of one database per Jest worker on the same server (agentdock_1_test, …), recreated on every run. In a board card’s session (NAUTELLE_CARD, set by the runner) the suite uses the card’s own database instead — agentdock_card<id>_test — so card worktrees never wipe each other’s (#389) — with a bounded pool (connection_limit=5&pool_timeout=10, #487). docs/guides/e2e-test-database.md. |
REDIS_URL |
"redis://:@localhost:6421/0" |
set | Redis for BullMQ (cron schedules, ADR-0015). Required unless SCHEDULER_ENABLED=false; the API refuses to boot without it. The password is REDIS_PASSWORD of docker/.env. |
TEST_REDIS_URL |
"redis://:@localhost:6421/1" |
set | Redis the e2e tests use. Each run keeps to its own key prefix and removes it on teardown. |
QUEUE_PREFIX |
agentdock |
optional | Key prefix of every queue. Default: agentdock. The e2e suite sets a per-run one. |
BUN_BIN |
— | optional | The runner e2e test starts the real runner daemon (apps/runner) with Bun. Defaults to bun on PATH. |
NAUTELLE_DATA_DIR |
./data |
optional | Directory of the API’s own state (C-366). At start-up each of APP_ENCRYPTION_KEY, AUDIT_HMAC_KEY and LLM_SECRETS_KEY that is not set here is read from <dir>/secrets.json, or generated (32 random bytes, base64) and written there with mode 600 — a variable set here always wins and a key in the file is never replaced. A key is missing and the directory is not writable: the API refuses to start. Back the file up — data sealed with a key is lost without it — but not next to the database dump: either alone is useless. Default: /data in the image (NODE_ENV=production), else ./data. |
AUDIT_HMAC_KEY |
— | optional | Key of the audit hash chain (C-174): every new audit record is hashed with HMAC-SHA256 under it, so someone who can write the database but lacks the key cannot rewrite a record and recompute the chain after it. At least 32 random bytes (openssl rand -base64 32); keep it out of the database and its backups. Records written before it was set keep their plain sha256 and still verify. Unset: the API takes it from, or generates it into, NAUTELLE_DATA_DIR’s secrets.json (below). Never change or drop it once set: verification then fails at the first keyed record. |
AUDIT_VERIFY_CRON |
"0 3 * * *" |
optional | Cron schedule of the audit chain verification (server time). Default: nightly at 03:00. |
USAGE_RECOMPUTE_BATCH |
1000 |
optional | Requests re-priced per transaction by a price recompute (spec 13 D7). Default: 1000. |
APP_ENCRYPTION_KEY |
— | optional | Key that seals secrets stored by Nautelle — the Telegram bot token (spec 22 D8), Slack webhook URLs (ADR-0109) and two-factor sign-in secrets (ADR-0081): 32 random bytes, base64 (openssl rand -base64 32). Without it the Telegram and Slack integrations refuse to store a token or URL and two-factor sign-in cannot be set up or used. Keep it out of backups of the database: either alone is useless, both together are the secret. Unset: taken from, or generated into, NAUTELLE_DATA_DIR’s secrets.json. |
APP_URL |
http://localhost:3517 |
optional | Public URL of the web app, used in Telegram message links (spec 22 D10), in email links (C-404), in Slack message links (ADR-0109), in emailed password reset links (C-402: without it Forgot password is off) and as the Telegram Mini App’s address when it is https (#580). |
TELEGRAM_POLL_TIMEOUT_S |
30 |
optional | Telegram long-poll timeout, seconds (spec 22 D7). Default: 30. |
TELEGRAM_API_BASE |
https://api.telegram.org |
optional | Telegram Bot API base URL. Default: https://api.telegram.org — tests point it at a mock. |
TELEGRAM_INIT_DATA_MAX_AGE_S |
3600 |
optional | How old, in seconds, a Telegram Mini App’s initData may be when it signs in (#580): 60 to 86400. Default: 3600. The Mini App needs an https APP_URL; with any other the bot sends plain links instead of Mini App buttons. |
SMTP_URL |
smtps://user:password@smtp.example.com:465 |
optional | Email notifications (C-404, ADR-0108). Unset: no email is sent, the Email column stays hidden and Forgot password tells people to ask an admin (C-402). smtp:// upgrades with STARTTLS when the server offers it; smtps:// is TLS from the start (usually port 465). Percent-encode an @ or : in the user or password. The credentials are never shown or logged. |
SMTP_FROM |
Nautelle <nautelle@example.com> |
optional | Sender of every email. Default: “Nautelle <user>” when the SMTP user is an address, else “Nautelle <nautelle@localhost>”. |
ACTIVITY_POLL_MS |
2000 |
optional | Activity feed and run history projector poll interval, in ms (spec 21 D2). 0 turns the poll loop off. Default: 2000. It never runs under APP_ENV=test. |
ACTIVITY_GAP_GRACE_MS |
10000 |
optional | How long a hole in events.id is waited on before it is treated as a rolled-back id, in ms (spec 21 notes). Default: 10000. |
ACTIVITY_RETENTION_DAYS |
180 |
optional | Days an activity item is kept; a daily job deletes older ones (spec 21 D6). Default: 180. |
SCHEDULER_ENABLED |
true |
optional | Cron schedules (spec 25, 123): false runs no schedules worker on this instance and needs no REDIS_URL (maintenance). Default: true. It never runs under APP_ENV=test. |
TERMINAL_IDLE_TIMEOUT_SECTERMINAL_MAX_DURATION_SEC |
90014400 |
optional | Interactive terminal attach (spec 29 D7), in seconds. An attach with no input (read-write) or no traffic at all (read-only) for the idle timeout closes with idle; any attach closes with max_duration at the maximum. Defaults: 900, 14400. |
APP_TIMEZONE |
Europe/Berlin |
optional | Default time zone (IANA) a new budget’s day, week or month is cut in (spec 28 D1). Default: UTC; an unknown zone falls back to UTC. |
WEBHOOKS_WORKER_ENABLED |
true |
optional | Webhooks (spec 26). false keeps the outbound dispatcher and delivery worker off on this instance (inbound triggers still answer). Default: true. They never run under APP_ENV=test. Trigger and webhook secrets need APP_ENCRYPTION_KEY. |
PUBLIC_URL |
https://dock.example.com |
optional | GitHub App (spec 27). Public HTTPS base GitHub can reach for POST /hooks/github (docs/architecture/github-app.md). Unset: the App’s hook is created inactive and every project keeps 60 s polling. The App’s secrets need APP_ENCRYPTION_KEY; the manifest redirect uses APP_URL (else WEB_URL). |
GITHUB_API_BASE |
https://api.github.com |
optional | GitHub REST API base. Default: https://api.github.com — tests point it at a mock. |
ATTACHMENTS_DIR |
./data/attachments |
optional | Board card attachments (#309, ADR-0031). Where the API stores the bytes (<projectId>/<attachmentId>, uploads in tmp/); back it up with the database. Default: ./data/attachments, relative to the API’s working directory. |
ATTACHMENTS_PROJECT_QUOTA_BYTES |
1073741824 |
optional | Bytes one project may store, pending uploads included. Default: 1073741824 (1 GiB). |
ATTACHMENTS_USER_PENDING_BYTES |
268435456 |
optional | Bytes of uploads not yet claimed by a card (and not expired) one user may hold; above it an upload is refused with 413 pending_exceeded (#156). Default: 268435456 (256 MiB). |
NAUTELLE_UPDATE_CHECK |
true |
optional | Update check (C-374, ADR-0091). Once a day the API asks GitHub for the latest Nautelle release (api.github.com, no data about the install) and admins see a notice when a newer one is out. false turns it off: GitHub is never asked. |
DEEPL_API_KEY |
empty | set | DeepL API key (#324, ADR-0033). Set: the free text people save is translated to English by DeepL inside the save (the text leaves this host for DeepL); a free key ends in :fx. Empty: the runner’s haiku translates it later (ADR-0027). Server-side only — never logged, returned or sent to a runner. |
SPEECH_TO_TEXT_URLSPEECH_TO_TEXT_API_KEYSPEECH_TO_TEXT_MODEL |
https://api.openai.com/v1— whisper-1 |
optional | Voice input of the Telegram /new intake (#594). The base URL of a Whisper-compatible speech-to-text API — the bot posts voice messages to <base>/audio/transcriptions (OpenAI-style multipart) and uses the text. The audio leaves this host for that service and is never stored. Unset: the bot answers that voice input is off. The key is sent as a bearer token (optional for a local server); server-side only. Model default: whisper-1. |
LLM_SECRETS_KEY |
— | optional | Master key of the LLM gateway (ADR-0059, #598): seals the AI provider keys set under Admin › AI providers with AES-256-GCM. 32 random bytes, base64 (openssl rand -base64 32). Unset: taken from, or generated into, NAUTELLE_DATA_DIR’s secrets.json. Malformed: the gateway does not start — no AI feature runs and no provider key can be stored — and the rest of the API runs on. Separate from APP_ENCRYPTION_KEY; keep it out of database backups: either alone is useless, both together are the keys. |
ADMIN_EMAILADMIN_PASSWORDADMIN_NAME |
— — — |
optional | The first admin without the browser (C-367): set both, and the API creates that admin on boot while no user exists — else it logs a one-time <WEB_URL>/setup?token=… link. bun run admin:create reads them too, to skip its prompts. Remove them once the admin exists. |
DEMO_LOGIN_EMAILDEMO_LOGIN_PASSWORD |
— — |
optional | Demo recordings (C-383, ADR-0090): scripts/demo-dev.sh runs the API on a database of its own (agentdock_demo_<card>) and creates its admin from these; bun run demo:db seed gives that admin the demo project. The runner sets them from the project’s Demo env — never put a real account here. |
DEMO_API_PORT |
— | optional | The demo API’s port; default the web app’s PORT + 1000. |
Web app
Section titled “Web app”The published web image is built for any host and needs none of these. They matter when you build the web app yourself or run it from source. Source: apps/web/.env.example.
| Variable | Value in the example | Description | |
|---|---|---|---|
API_URL |
http://localhost:8180 |
set | Target of the same-origin /api rewrite (next.config.ts). |
NEXT_PUBLIC_LIVE_URL |
ws://localhost:8180/live |
set | WebSocket URL of the API’s /live endpoint (live updates). Development default shown; in production use wss://<origin>/api/live through the reverse proxy. |