Repository navigation
Deploying with Portainer
If you manage Docker through Portainer, use the
Portainer-tailored compose file — docker-compose.portainer.yml — with the
Web editor method described below, instead of the plain
Docker Compose CLI path. It's fully
self-contained: it pulls the published image (no build) and embeds the two
files the CLI compose bind-mounts (the Postgres init script for the RLS
role, and the resolver config) directly in the YAML, so there's nothing to
clone.
With a Repository stack, Portainer only reads a
stack.envfile committed in the repo — the variables you type into Portainer's UI are silently ignored, so${...}placeholders fall back to their defaults. This is the single most common cause of "I setAPI_HOST_PORTbut it's still trying to bind:8000" reports. The Web editor builds the stack's environment from the variables you actually set in Portainer, so they apply correctly.
Stacks → Add stack → Web editor, and paste the contents of
docker-compose.portainer.yml.
Under Environment variables, add the settings below — they fill the
${...} placeholders in the compose file, the same options documented
inline in .env.example
and in the Configuration Reference:
| Variable | Required | Notes |
|---|---|---|
FERNET_KEY |
Yes | Encrypts TOTP secrets & stored credentials at rest. Generate: openssl rand -base64 32 | tr '+/' '-_'
|
PUBLIC_BASE_URL |
Yes | Public HTTPS URL your reverse proxy serves this at, e.g. https://dmarc.yourdomain.com
|
POSTGRES_PASSWORD |
Yes | Any strong password |
DMARC_APP_DB_PASSWORD |
Yes | A second, different strong password |
PLATFORM_ADMIN_BOOTSTRAP_EMAIL / PLATFORM_ADMIN_BOOTSTRAP_PASSWORD
|
Recommended | Your first admin login (a no-op once an admin exists — change the password after first sign-in) |
FORWARDED_ALLOW_IPS |
Recommended | Your reverse proxy's IP, so real client IPs (not the proxy's) get logged |
IMAGE_TAG |
Optional | Image tag to run (default latest); pin to a release tag like v0.1.4 if you prefer |
API_HOST_PORT |
Optional | Host port to publish (default 8000); change if 8000 is taken |
ENTRA_MAIL_CLIENT_ID / ENTRA_MAIL_CLIENT_SECRET
|
Optional | Report ingestion via Microsoft Graph — leave unset for DNS-checks-only |
ENTRA_SSO_CLIENT_ID / ENTRA_SSO_CLIENT_SECRET, CLOUDFLARE_*, HOSTED_REPORTS_*, SECURITY_CONTACT_EMAIL, MTA_STS_POLICY_*
|
Optional | Feature-gated — leave unset to keep the feature off |
migrate runs first (schema migrations + bootstrapping the admin account),
then api and worker start. If :8000 is already in use on the host
(e.g. another instance), set API_HOST_PORT to a free port. Point your
reverse proxy's upstream at this host's :8000 (or your API_HOST_PORT).
By hand: open the stack, change IMAGE_TAG under Environment variables
to the new version, and click Update the stack with Re-pull image
on. That runs the database migrations and restarts the app.
With Update now (from v0.2.0): the admin console's Update now button
can do the same through Portainer's API, so Portainer stays in charge of the
stack. The stack's updater container does the asking; the app itself
never holds the Portainer token. To set it up, add these variables to the
stack and redeploy once:
| Variable | Value |
|---|---|
UPDATER_SHARED_SECRET |
A random secret shared by the app and the updater: openssl rand -hex 32
|
PORTAINER_URL |
Portainer's address as the updater can reach it, e.g. http://<docker host IP>:9000, or https://portainer.example.com
|
PORTAINER_API_KEY |
A Portainer access token: in Portainer, your user menu → My account → Access tokens → Add access token |
PORTAINER_STACK_ID |
The stack's number: the id= in its address when you open it in Portainer |
PORTAINER_TLS_VERIFY |
Optional, default true. Portainer's own HTTPS certificate is self-signed out of the box: use its http:// port, a trusted certificate, or set this to false
|
Keep the token tight. An access token can do whatever its Portainer user can, so don't create it for your main admin. In Portainer (Community Edition is enough):
-
Settings → Users: add a standard user, e.g.
yadt-updater. - Settings → Teams: add a team with that user in it.
- Environments: give the team access to this environment.
- The YetAnotherDmarcTool stack → Access control: restrict it to that team (otherwise only administrators can update it, and the token is refused).
- Sign in as that user and create the access token there.
That token can then redeploy this stack only: other stacks that aren't shared with the team stay out of its reach.
What Update now does: it checks the version is a real, newer release,
then asks Portainer to redeploy this stack with that IMAGE_TAG and fresh
images. Portainer then runs the migrations, restarts the app and replaces
the updater too. Problems show in the updater container's log, and in the
api's log as SETUP TIP / SETUP PROBLEM lines. Stacks deployed from a Git
repository aren't supported this way: update those in Portainer.
Onboarding Your First Organization
← Back to the README · Live demo · Report an issue · MIT licensed
- Quick Start: Docker Compose
- Deploying with Portainer
- Deploying on Azure
- Configuration Reference
- Onboarding an Organization
- IMAP Mailbox
- Google Workspace Mailbox
- Sign-in Providers
- Multi-Tenancy & RLS
- DMARC Subdomain Inheritance
- Report Attribution
- SPF Conditional Mode
- Detected Domains
- DNS Check Scheduling
- Domain Rating
🏗️ Architecture
🗺️ Roadmap