Skip to main content

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.

0 of 6 complete

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:

.env
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:

  1. Open your router's admin page in a browser - commonly http://192.168.1.1 or http://192.168.0.1 (check the sticker on the router, or its manual, if neither works).
  2. Find the Port Forwarding section - sometimes labeled NAT, Virtual Server, or Applications & Gaming; the exact name and location vary by router brand.
  3. Add two forwarding rules, both pointing at your Docker host's local (LAN) IP address:
    • External/WAN port 80 → Internal/LAN port 80
    • External/WAN port 443 → Internal/LAN port 443
  4. 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:

.env
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.

Already running Caddy, nginx, or Traefik for something else on this machine?

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.

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.