Deploy with Docker
This is the self-hosted path: you run dotMARC yourself, with PostgreSQL and (optionally) a Caddy reverse proxy for automatic HTTPS, on your own hardware, a home/office server, or a cloud VPS. Prefer a managed platform instead? See Deploy to Azure. Both paths need the two Entra app registrations from Getting Started first.
What you'll need
- Docker installed and running - Docker Desktop on Windows/Mac, or Docker Engine on Linux.
- A domain name (or a subdomain of one you already own) to point at wherever dotMARC will run -
for example
dotmarc.example.uk. You'll need this before you start: Entra's sign-in redirect and the TLS certificate in step 2 both depend on it. - Somewhere for dotMARC to run continuously - a server or NAS on your home or office network, or a cloud VPS/VM. Both are covered below.
1. Start dotMARC and PostgreSQL
$env:GRAPH_CLIENT_ID = '...'
$env:GRAPH_TENANT_ID = '...'
$env:GRAPH_CLIENT_SECRET = '...'
$env:GRAPH_MAILBOX_ADDRESS = '...'
$env:ENTRAID_TENANT_ID = '...'
$env:ENTRAID_CLIENT_ID = '...'
$env:ENTRAID_CLIENT_SECRET = '...'
$env:INITIAL_ADMIN_EMAILS = '...'
docker compose up
This runs dotMARC and a PostgreSQL 18 database together, with Postgres data persisted in a named
Docker volume (dotmarc-postgres-data). INITIAL_ADMIN_EMAILS is how you set the
InitialAdmins__Emails config value described in Getting Started
via Docker Compose specifically - docker-compose.yml maps this shell variable onto the
InitialAdmins__Emails app setting, so the naming differs in casing/format even though it's the
same setting.
Typing all those $env: lines every time gets old fast. Instead, create a file named .env (no
filename before the dot) next to docker-compose.yml - Compose reads it automatically, so you can
just run docker compose up on its own from then on:
GRAPH_CLIENT_ID=11111111-1111-1111-1111-111111111111
GRAPH_TENANT_ID=22222222-2222-2222-2222-222222222222
GRAPH_CLIENT_SECRET=your-mailbox-app-client-secret
ENTRAID_TENANT_ID=22222222-2222-2222-2222-222222222222
ENTRAID_CLIENT_ID=33333333-3333-3333-3333-333333333333
ENTRAID_CLIENT_SECRET=your-dashboard-app-client-secret
(No quotes, no $env: prefix - just KEY=value, one per line. The GUIDs above are placeholders;
use the real client/tenant IDs from Getting Started.)
2. Make dotMARC reachable from the internet
dotMARC itself only speaks plain HTTP, on port 8080 inside the container - fine for testing on your
own machine at http://localhost:8080, but two more things need to be true before real sign-in and
DMARC report delivery will work: your dashboard needs a real hostname with a valid HTTPS
certificate, and that hostname needs to actually be reachable from the internet. This section walks
through both, step by step.
2a. Point your domain at your server
Add a DNS A record for the hostname you chose above (e.g. dotmarc.example.uk), pointing at
your server's public IP address.
- Don't know your public IP? Visit a site like whatismyipaddress.com from the same network your server is on.
- Home or office internet connection: most residential and small-business connections don't have a fixed IP address - it can change without warning. If yours does, use a free Dynamic DNS service (e.g. DuckDNS, or your router's built-in DDNS feature if it has one) instead of a plain A record - it keeps your hostname pointed at your current IP automatically.
- Cloud VPS/VM: these almost always come with a fixed IP already, so a plain A record is fine.
2b. Open ports 80 and 443
The internet needs a path to your server on port 80 (used briefly to prove you control the domain when requesting a certificate) and port 443 (regular HTTPS traffic). How you open that path depends on where dotMARC is running:
Home or office server, behind a router:
- Open your router's admin page in a browser - commonly
http://192.168.1.1orhttp://192.168.0.1(check the sticker on the router, or its manual, if neither works). - Find the Port Forwarding section - sometimes labeled NAT, Virtual Server, or Applications & Gaming; the exact name and location vary by router brand.
- Add two forwarding rules, both pointing at your Docker host's local (LAN) IP address:
- External/WAN port
80→ Internal/LAN port80 - External/WAN port
443→ Internal/LAN port443
- External/WAN port
- Save the changes - some routers need a restart to apply them.
Cloud VPS or virtual machine:
Open inbound access to TCP ports 80 and 443 in whatever firewall your provider uses:
- Plain Linux firewall (
ufw):sudo ufw allow 80,443/tcp - AWS: add inbound rules for TCP 80 and 443 to the instance's Security Group
- Azure VM: add inbound port rules for 80 and 443 to the VM's Network Security Group
- DigitalOcean and most others: look for Firewall in the control panel and add rules allowing TCP 80/443 in
2c. Put Caddy in front of dotMARC
Caddy is a reverse proxy that gets you a real, automatically-renewing HTTPS certificate (via Let's Encrypt) with almost no configuration - the easiest option if you don't already run a reverse proxy for something else on this machine.
The repo ships a complete, ready-to-run stack for this: docker-compose.selfhosted.yml, a
standalone alternative to the plain docker-compose.yml from step 1 that adds Caddy, already fully
wired up. Nothing in it needs hand-editing - stop whatever you started in step 1 first
(docker compose down), then add one more line to the same .env file from step 1:
DOTMARC_HOSTNAME=dotmarc.example.uk
(Replace dotmarc.example.uk with your own hostname from step 2a - this is the only thing that
changes between deployments; everything else in docker-compose.selfhosted.yml and its
Caddyfile.selfhosted is generic.)
Then run:
docker compose -f docker-compose.selfhosted.yml up -d
That's it. Caddy requests and renews a certificate for DOTMARC_HOSTNAME automatically, and
already sends the X-Forwarded-For/X-Forwarded-Proto headers dotMARC needs to know it's being
reached over HTTPS - which is exactly what Entra's redirect-URI check depends on (without it,
sign-in fails with AADSTS50011). Certificate issuance only succeeds once ports 80/443 are genuinely
reachable from the internet (step 2b), since that's how Let's Encrypt confirms you control the
domain.
Skip this file entirely and keep using the plain docker-compose.yml from step 1 (which only
publishes port 8080, no TLS) - instead, point your existing reverse proxy at app:8080 (if it's
on the same Docker network) or localhost:8080 (if you publish that port). Whatever you use just
needs to forward X-Forwarded-For and X-Forwarded-Proto. Caddyfile.selfhosted in this repo is
still a useful reference for the exact config shape, even if you're not running it directly.
2d. Check it worked
From a device on a different network than your server - your phone on mobile data works well,
not the same Wi-Fi - browse to https://dotmarc.example.uk (your own hostname). You should see
dotMARC's sign-in page with a valid padlock icon and no certificate warning.
- Connection times out or refuses to connect: re-check step 2b (the ports aren't actually open).
- Certificate warning/error: re-check step 2a (DNS isn't pointing at the right IP yet), or give Caddy another minute or two to finish requesting the certificate.
What's next
Want dotMARC to host MTA-STS policies for your customers' domains? Caddyfile.selfhosted (used
above) already includes everything that needs - see MTA-STS: hosting infrastructure
for the couple of settings to turn it on.
Point each monitored domain's DMARC record's rua= tag at the same mailbox this app polls, e.g.:
v=DMARC1; p=quarantine; rua=mailto:[email protected]
See Getting Started for what the dashboard shows once reports start arriving. Next: DNS Provider Push to let dotMARC fix DNS records for you, or Local Development to run and test from source.