The default docker-compose.yml now starts this stack:
| Service | Why |
|---|---|
| WildDuck | IMAP, POP3, API |
| WildDuck Webmail | Webmail and account management |
| ZoneMTA | Outbound SMTP |
| Haraka | Inbound SMTP on port 25 |
| Rspamd | Spam scoring and message classification for inbound mail |
| Traefik | TCP routing for 25 and TLS termination/routing for 443, 993, 995, and 465 |
| MongoDB | Primary database |
| Redis | Shared cache / queue state |
WildDuck and ZoneMTA are exposed as implicit TLS only through Traefik. Haraka is also published through Traefik on port 25 and can provide STARTTLS with its own certificate files.
- Use
example.envas the template for your local.env. - Set
PUBLIC_HOSTNAME,MAIL_DOMAIN, and the required WildDuck secrets. If your SMTP PTR/HELO name should differ from the client-facing hostname, also setSMTP_HOSTNAME. - Set Traefik TLS mode:
TRAEFIK_TLS_MODE=fileand pointTRAEFIK_CERT_FILE/TRAEFIK_KEY_FILEat PEM files under./certs/, orTRAEFIK_TLS_MODE=acmeand setTRAEFIK_CERT_RESOLVERplusTRAEFIK_ACME_EMAIL. - If you want STARTTLS on port 25, keep Haraka's TLS files under
./certs/. InTRAEFIK_TLS_MODE=acme, Haraka reads Traefik's issued certificate directly from the shared ACME storage../setup-scripts/bootstrap.sh certsthen only restarts Haraka when Traefik has renewed the certificate. - Start the stack with
docker compose up -d --build. - Run
./setup-scripts/bootstrap.sh allto print DNS records, ensure DKIM exists, and create the first user.
The checked-in certs under certs/ are only suitable for local development.
The compose file mounts the checked-in defaults from default-config/ where the apps still expect a config tree, then overrides runtime settings with environment variables:
- WildDuck, ZoneMTA, and WildDuck Webmail use
APPCONF_...variables provided bywild-config. - Traefik routing and TLS are controlled from env through its file provider template in
dynamic_conf/dynamic.yml. - Traefik startup config is rendered by
container-scripts/traefik-entrypoint.sh, similar to Haraka's generated runtime config flow. - Traefik forwards all plain SMTP traffic on port
25to Haraka with a TCP catch-all router. - Traefik accepts incoming PROXY protocol on its public entrypoints only if
TRAEFIK_PROXY_PROTOCOL_TRUSTED_IPSis explicitly set. - Traefik forwards PROXY protocol v1 to Haraka SMTP, WildDuck IMAP/POP3, and ZoneMTA submission; WildDuck Webmail stays on HTTP forwarded headers via
APPCONF_www_proxy. TRAEFIK_TLS_MODE=fileuses certificate files from the mounted./certsdirectory.TRAEFIK_TLS_MODE=acmeenables a Traefik ACME resolver and router-levelcertResolver.- Haraka is built from
docker-images/haraka/Dockerfileand renders its runtime config from the templates underdocker-images/haraka/config. - The Haraka image entrypoint launches the Haraka CLI with
-c /run/haraka, so the generated config directory is actually used. - Haraka now talks to the bundled
rspamdservice through generatedrspamd.ini, so inbound scanning works without shipping a static Haraka config tree. smtp.inistays overrideable through the scalar env varsHARAKA_SMTP_LISTENandHARAKA_SMTP_NODES.SMTP_HOSTNAMEoverrides the hostname used by ZoneMTA and Haraka for SMTP identity and PTR-aligned DNS guidance; when unset it falls back toPUBLIC_HOSTNAME.- Haraka's
wildduckplugin handles recipient validation and storage, so placingrcpt_to.in_host_listahead of it will accept SMTP recipients before WildDuck can create the inbox delivery target.
That means you do not need setup.sh to bring up the default stack.
If you need more application settings, add more APPCONF_... entries in docker-compose.yml. Nested keys use underscores, for example APPCONF_imap_setup_hostname.
setup-scripts/bootstrap.sh is the replacement for the old setup.sh follow-up steps. It does not rewrite compose files or generated config trees. Instead it reads the current .env, waits for the API, then uses the WildDuck API directly. It requires curl and node on the host for API tasks, and docker for the certs mode.
Examples:
./setup-scripts/bootstrap.sh all./setup-scripts/bootstrap.sh dns./setup-scripts/bootstrap.sh dkim./setup-scripts/bootstrap.sh user./setup-scripts/bootstrap.sh certs./setup-scripts/bootstrap.sh certs --install-cron
What it does:
dnsensures a DKIM key exists forMAIL_DOMAIN, then writes the recommendedA/AAAA,MX,SPF,DKIM,DMARC, andPTRguidance to.bootstrap/<mail-domain>-dns.txtusingSMTP_HOSTNAMEwhen set, otherwisePUBLIC_HOSTNAMEdkimensures a DKIM key exists and prints just the DKIM TXT record to.bootstrap/<mail-domain>-dkim.txtusercreates the first mailbox throughPOST /usersallruns DNS/DKIM first and then creates the first user ifFIRST_USER_*values are configured or the shell is interactivecertssyncs file-based Haraka TLS targets from Traefik and, in ACME mode, restarts Haraka only when Traefik's issued certificate changed
In TRAEFIK_TLS_MODE=file, certs copies from TRAEFIK_CERT_FILE / TRAEFIK_KEY_FILE unless Haraka already points at the same paths. In TRAEFIK_TLS_MODE=acme, Haraka reads Traefik's acme.json directly from the shared Docker volume on startup, and certs restarts Haraka when the certificate for BOOTSTRAP_HARAKA_CERT_DOMAIN or PUBLIC_HOSTNAME changed.
The bundled compose file mounts ./certs as a directory into Traefik and Haraka, so Docker no longer creates PEM-named directories when the certificate files do not exist yet.
./setup-scripts/bootstrap.sh certs --install-cron also installs a host cron job that reruns the sync on a schedule, so Haraka picks up future Traefik renewals as well. The default schedule is 17 */12 * * *.
Optional .env values for non-interactive runs are documented in example.env. The helper defaults to http://127.0.0.1:${WILDDUCK_API_PORT:-8080} and will reuse an existing DKIM key unless BOOTSTRAP_DKIM_REPLACE=true.
For local .test style development, you will usually point PUBLIC_HOSTNAME and MAIL_DOMAIN to 127.0.0.1 in your host /etc/hosts so the browser can reach Traefik. That breaks self-delivery from ZoneMTA, because inside the container 127.0.0.1 is the container itself, not Haraka.
Use different host-side mappings for the web hostname and the mail domain instead:
127.0.0.1 mail.wildduck.dockerized.test
172.17.0.1 wildduck.dockerized.test
That keeps the browser path on localhost while letting ZoneMTA resolve the recipient domain back to the Docker host for SMTP on port 25, where Traefik forwards it to Haraka.
If you change the host mappings after ZoneMTA has already tried delivery, clear the cached DNS answer and resend:
docker compose exec redis redis-cli -n 2 DEL dns:resolve_wildduck.dockerized.test_AIf you use the bundled development certs, import certs/rootCA.pem into your mail client or browser trust store.