Skip to content

Configuration Reference

BJD1997 edited this page Oct 16, 2026 · 12 revisions

Configuration Reference

Every environment variable YADT reads, grouped by what it's for. Source of truth is always .env.example in the repo — copy it to .env and fill in what you need; this page is the readable walkthrough of the same file.

TL;DR — the minimum to get a running dashboard: PUBLIC_BASE_URL, POSTGRES_PASSWORD, DMARC_APP_DB_PASSWORD, FERNET_KEY. That's local email+password+TOTP login and DNS best-practice checks, with nothing Microsoft-side to configure. Report ingestion (from either path) additionally needs ENTRA_MAIL_CLIENT_ID/ENTRA_MAIL_CLIENT_SECRET — without it, the dashboard runs fine but never ingests a report.

Core (required)

Variable Required What it does
PUBLIC_BASE_URL Yes The HTTPS URL your reverse proxy exposes this at, e.g. https://dmarc.yourdomain.com. Part of the Entra SSO OAuth redirect URI — get it right before enabling SSO.
POSTGRES_PASSWORD Yes Password for the Postgres owner role, used only by the one-off migrate container.
DMARC_APP_DB_PASSWORD Yes Password for the separate, non-superuser dmarc_app role the app actually connects as at runtime — this is what makes row-level security apply at all (superusers and table owners are exempt from RLS regardless of FORCE ROW LEVEL SECURITY).
FERNET_KEY Yes Encrypts TOTP secrets (every local-auth user, including the platform admin). Platform-admin login enrolls TOTP, so this fails closed without it. Back it up: losing it makes existing TOTP enrollments undecryptable (users then sign in with a recovery code, or an admin resets their MFA). To rotate, list the new key first and the old one after it, comma-separated. Generate: openssl rand -base64 32 | tr '+/' '-_' or python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())".
POSTGRES_USER / POSTGRES_DB No (defaults fine) The Postgres owner role name and database name.
API_BIND_ADDRESS Recommended The host address the API is published on. Default 0.0.0.0 is every interface, and Docker's published ports bypass UFW. Set it to the address your reverse proxy connects to (127.0.0.1 if the proxy is on the same host). See Securing Your Install.
WORKER_MEMORY_LIMIT Optional Memory limit for the worker container (default 1g). Report mailboxes are public, so a report built to use a lot of memory restarts the worker rather than slowing the whole server.
FORWARDED_ALLOW_IPS Recommended Your reverse proxy's own IP, so real client IPs — not the proxy's — get logged in sessions and the sign-in audit log. Leave unset (trusts nothing external) if you're not behind a reverse proxy.

Report ingestion — Entra App A: Mail Access

Variable Required What it does
ENTRA_MAIL_CLIENT_ID / ENTRA_MAIL_CLIENT_SECRET Required for report ingestion Not optional the way the rest of this page is. Both ways of getting reports in — an organization's own connected mailbox, and the operator-hosted mailbox — authenticate to Microsoft Graph through this one app-only app registration (client-credentials flow). Leave it unset and the dashboard still runs fine, DNS best-practice checks work with zero Microsoft-side config, but no report is ever ingested by either path.

Login — Entra App B: Dashboard SSO

Variable Required What it does
ENTRA_SSO_CLIENT_ID / ENTRA_SSO_CLIENT_SECRET Optional Enables "Sign in with Microsoft" (delegated OIDC) for organizations on Microsoft 365. Local email+password+TOTP login works the same either way.
GOOGLE_SSO_CLIENT_ID / GOOGLE_SSO_CLIENT_SECRET Optional (v0.3.0) Enables "Sign in with Google" for organizations on Google Workspace. See Sign-in Providers.
OIDC_ALLOW_PRIVATE_ISSUERS Optional, default false (v0.3.0) Lets organizations' own OpenID Connect providers be on a private address (and http://). Only when every organization on the server is trusted.

Google Workspace report mailboxes

Variable Required What it does
GOOGLE_MAIL_SERVICE_ACCOUNT_FILE / GOOGLE_MAIL_SERVICE_ACCOUNT_JSON Optional (v0.3.0) A Google Cloud service account's JSON key (a file path, or the JSON itself) for reading Google Workspace report mailboxes through domain-wide delegation. Needed in the api and the worker. See Connecting a Google Workspace Mailbox.

Operator-hosted reporting mailbox

Variable Required What it does
HOSTED_REPORTS_MAILBOX_ADDRESS Optional For organizations with no mailbox of their own to dedicate: issues <mailbox>+<tag>@<mailbox's domain> addresses that land in this shared mailbox via plus-addressing (the provider must deliver name+anything@domain to name@domain). The addresses always use the mailbox's own domain. Leave blank to disable the feature.
HOSTED_REPORTS_SOURCE Optional, default microsoft (v0.3.0) How the hosted mailbox is read: microsoft (a Microsoft 365 mailbox, needs HOSTED_REPORTS_TENANT_ID and the mailbox app), imap, or google (needs the Google service account below).
HOSTED_REPORTS_TENANT_ID With microsoft The Entra tenant the hosted Microsoft 365 mailbox is in.
HOSTED_REPORTS_IMAP_HOST, _PORT (993), _SECURITY (ssl/starttls), _USERNAME, _PASSWORD With imap (v0.3.0) The hosted mailbox's IMAP server. Set by you as platform admin, so a private address (your own mail server) is allowed.
HOSTED_REPORTS_IMAP_FOLDER / HOSTED_REPORTS_IMAP_PROCESSED_FOLDER Optional (INBOX / DMARC processed) Where reports arrive, and where they're moved once stored (created if missing).
HOSTED_REPORTS_IMAP_AUTHSERV_ID With imap The mail server's ID: the first word of the Authentication-Results header it adds to incoming mail. Only its checks count for the report sender check, and nothing is read until it's set; the hosted mailbox's sync error names the ID it found.
HOSTED_REPORTS_ADDRESS_DOMAIN Optional, normally unset Overrides the addresses' domain. Defaults to the mailbox's domain, which it has to match anyway.

Cloudflare (optional)

Variable Required What it does
CLOUDFLARE_API_TOKEN / CLOUDFLARE_ZONE_ID Optional Auto-creates, and removes again, the RFC 7489 §7.1 authorization record each domain using a hosted address needs, in the Cloudflare zone of the hosted mailbox's domain. Use a scoped API Token (Zone:DNS:Edit on that one zone, nothing else), never the legacy Global API Key. Also set the token's Client IP Address Filtering to the server's outbound IP and give it an expiry date, so a leaked token is useless elsewhere and doesn't last forever. Leave blank to fall back to manual setup instructions instead.

Platform admin bootstrap

Variable Required What it does
PLATFORM_ADMIN_BOOTSTRAP_EMAIL / PLATFORM_ADMIN_BOOTSTRAP_PASSWORD Recommended If no platform admin exists yet, the migrate container creates one from these on startup. Change the password after first login — these vars can then be left as-is; bootstrap is a no-op once an admin already exists.
PLATFORM_ADMIN_SESSION_IDLE_TIMEOUT_HOURS / PLATFORM_ADMIN_SESSION_ABSOLUTE_TIMEOUT_HOURS Optional (default 4 / 24) How long a break-glass admin sign-in lasts: ends after this many hours unused, and after the absolute limit however active. Normal sign-ins, including an operator organization's admins, keep 12 hours idle / 7 days. Deliberately not changeable from the admin console.
API_DOCS_ENABLED Optional (default false) Serves the interactive API docs at /docs, /redoc and /openapi.json. Off on public instances; turn on for local development.
PORTAINER_URL, PORTAINER_API_KEY, PORTAINER_STACK_ID, PORTAINER_TLS_VERIFY Optional, Portainer stacks only, on the updater service (from v0.2.0) Update now through Portainer's API; with UPDATER_SHARED_SECRET. See Deploying with Portainer.
STARTTLS_CHECK_MODE Optional (default probe) Where the STARTTLS result comes from. probe: connect to each MX host on port 25. tls_rpt: the TLS-RPT reports senders send (last 14 days; pending until the first ones arrive; no result for domains whose TLS-RPT doesn't reach the app). off: skip it. Use tls_rpt where outbound port 25 is blocked (Azure, most cloud hosts).
STARTTLS_CHECK_ENABLED Optional (default true) From v0.1.7. false turns the STARTTLS check off whatever STARTTLS_CHECK_MODE says.
UPDATE_REHEARSAL_ENABLED Optional (default false, Azure only) Shows Run test update under admin console → Updates: every in-app update step on the running version, nothing changes. Turn on to check the updater after a redeploy or a permission change.

security.txt (optional, RFC 9116)

Variable Required What it does
SECURITY_CONTACT_EMAIL Optional Publishes /.well-known/security.txt with this contact for vulnerability reports. Leave blank to not serve it — there's nothing RFC-9116-correct to publish without at least one contact.

This instance's own MTA-STS policy (optional)

Variable Required What it does
MTA_STS_POLICY_HOSTNAME / MTA_STS_POLICY_BODY Optional Not organization-facing — organizations publish/host their own via the Policy Builder, and this app stays read-only toward their DNS. This is only for the operator's own domain, if it sends mail and you want to publish its MTA-STS policy through this same app instance. MTA_STS_POLICY_HOSTNAME must exactly match the Host header of incoming requests. Leave both blank to not serve it at all.

Updates (optional)

Variable Required What it does
IMAGE_TAG Optional Which published tag docker compose pull/up fetches. Leave unset to track latest. Deliberately separate from APP_VERSION (baked into the image at build time) — never override APP_VERSION via env.
UPDATER_SHARED_SECRET Optional Enables the "Update now" button in the admin console — a container with Docker socket access (host-root-equivalent). Leave blank to not run it; the admin console still shows "update available" notices from the periodic check either way, just without a one-click trigger. Generate: openssl rand -hex 32.

Scale-out (optional — sensible defaults, only set when scaling)

See Scaling Horizontally for the full picture. Quick reference:

Variable Default What it does
WORKER_CONCURRENCY 4 Consumer loops per worker replica.
WORKER_QUEUE_POLL_INTERVAL_SECONDS 5 Idle queue poll interval.
WORKER_JOB_STALE_SECONDS 1800 Reclaim a job stuck running this long (i.e. its worker crashed) — must exceed your longest job.
WORKER_HEALTH_PORT 8080 Liveness endpoint port.
LEADER_DATABASE_URL unset → falls back to DATABASE_URL Set to a direct, unpooled Postgres connection if a transaction-mode pooler (e.g. PgBouncer) sits in front of DATABASE_URL — the leader's advisory lock is session-scoped and misbehaves under transaction pooling.
RATE_LIMIT_BACKEND memory Set to postgres when running multiple api replicas, so the auth rate limiter is shared and correct across them instead of in-memory-per-process.
DATABASE_READ_URL unset Offloads report/trend/posture dashboard reads to a second connection. Entirely opt-in — see Scaling Horizontally for what's actually safe to point this at today.

← Deploying on Azure · Onboarding Your First Organization →

Clone this wiki locally