The default docker-compose.yml starts a complete WildDuck mail stack:
| Service | Purpose |
|---|---|
| WildDuck | IMAP, POP3, and API |
| WildDuck Webmail | Webmail and account management |
| ZoneMTA | Outbound SMTP |
| Kirin | Inbound SMTP on port 25 |
| Rspamd | Inbound spam scoring and classification |
| Traefik | Web routing, mail TLS termination, and SMTP proxying |
| MongoDB | Primary database |
| Redis | Shared cache and queue state |
WildDuck and ZoneMTA use implicit TLS through Traefik. Traefik forwards port 25 to Kirin with PROXY protocol v1, while Kirin provides STARTTLS with the certificate files mounted from ./certs.
-
Copy
example.envto.env. -
Set
PUBLIC_HOSTNAME,MAIL_DOMAIN, and every required WildDuck secret. SetSMTP_HOSTNAMEtoo if the SMTP PTR/HELO name differs from the client-facing hostname. -
Choose a Traefik TLS mode:
- For local files, keep
TRAEFIK_TLS_MODE=fileand pointTRAEFIK_CERT_FILEandTRAEFIK_KEY_FILEto PEM files under./certs. - For Let's Encrypt, set
TRAEFIK_TLS_MODE=acme,TRAEFIK_CERT_RESOLVER, andTRAEFIK_ACME_EMAIL.
- For local files, keep
-
Start the stack:
docker compose up -d --build
-
Create DKIM material, print the required DNS records, and optionally create the first mailbox:
./setup-scripts/bootstrap.sh all
-
In ACME mode, visit
https://$PUBLIC_HOSTNAME/once so Traefik requests the certificate. Then export it for Kirin and install the renewal sync job:./setup-scripts/bootstrap.sh certs --install-cron
The development certificates under certs/ are not suitable for production.
The Compose build is based on ghcr.io/zone-eu/kirin:0.1.3. During the image build it installs these published npm packages:
@zone-eu/kirin-plugin-rspamd@0.1.0@zone-eu/kirin-plugin-wildduck@0.1.1
No Kirin or plugin source checkout is required in this repository. Rspamd runs first at ordering 50. The WildDuck receiver runs at ordering 100 to validate recipients and store accepted messages.
Kirin is configured through APPCONF_... environment variables in docker-compose.yml. Its optional database overrides are KIRIN_MONGO_URL and KIRIN_SENDER_DB. Its STARTTLS paths are KIRIN_TLS_CERT_FILE and KIRIN_TLS_KEY_FILE.
- WildDuck, ZoneMTA, WildDuck Webmail, and Kirin use
APPCONF_...overrides provided bywild-config. - Traefik configuration is rendered by
container-scripts/traefik-entrypoint.shand the file-provider template indynamic_conf/dynamic.yml. - Traefik terminates TLS for HTTPS, IMAPS, POP3S, and SMTPS. Kirin terminates STARTTLS itself on port 25.
- Traefik accepts PROXY protocol on public entrypoints only when
TRAEFIK_PROXY_PROTOCOL_TRUSTED_IPSis explicitly set. It forwards PROXY protocol v1 to Kirin, WildDuck, and ZoneMTA. SMTP_HOSTNAMEcontrols the SMTP identity used by Kirin and ZoneMTA. It falls back toPUBLIC_HOSTNAME.- Additional nested settings can be expressed as
APPCONF_...keys, for exampleAPPCONF_imap_setup_hostname.
The default stack does not require the legacy setup generator.
The previous Haraka-based Compose workflow is isolated under legacy-haraka/. Run legacy-haraka/setup.sh to generate its configuration. Its docker-compose-w-setup.yml, certificate updater, and helper scripts apply only to that legacy stack.
See legacy-haraka/README.md for configuration, VPS production deployment, TLS, DNS, operation, updates, and troubleshooting.
setup-scripts/bootstrap.sh reads .env, waits for the WildDuck API when needed, and supports these modes:
all: ensure DKIM, write DNS guidance, and create the first user when configured or interactivedns: ensure DKIM and write A/AAAA, MX, SPF, DKIM, DMARC, and PTR guidancedkim: ensure DKIM and write only the DKIM recorduser: create the first mailbox through the WildDuck APIcerts: synchronize Traefik's certificate files to Kirin and restart Kirin only when they change
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-cronIn file mode, certs copies from TRAEFIK_CERT_FILE and TRAEFIK_KEY_FILE unless Kirin already uses those exact paths. In ACME mode, it exports the certificate selected by BOOTSTRAP_KIRIN_CERT_DOMAIN (or PUBLIC_HOSTNAME) from Traefik's acme.json. The default renewal check schedule is 17 */12 * * *.
The helper requires curl and node for API tasks, plus Docker for certificate synchronization. Optional non-interactive settings are documented in example.env.
Mapping both the public hostname and mail domain to 127.0.0.1 breaks local self-delivery: inside ZoneMTA, that address refers to the ZoneMTA container. Use separate host mappings, for example:
127.0.0.1 mail.wildduck.dockerized.test
172.17.0.1 wildduck.dockerized.test
This keeps browser traffic on localhost while allowing ZoneMTA to resolve the recipient domain back to the Docker host, where Traefik forwards port 25 to Kirin.
If the mapping changes after a delivery attempt, clear ZoneMTA's cached DNS answer before retrying:
docker compose exec redis redis-cli -n 2 DEL dns:resolve_wildduck.dockerized.test_AFor local development, import certs/rootCA.pem into your mail client or browser trust store.