From f6245f84dc2edcce8c757dc4007a58ca7ac0f725 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Thu, 16 Jul 2026 17:50:38 -0600 Subject: [PATCH] feat: add Mailu template Mailu 2024.06 mail server: front (nginx), admin, imap (dovecot), smtp (postfix), antispam (rspamd), Roundcube webmail and redis. Co-Authored-By: Claude Fable 5 --- blueprints/mailu/docker-compose.yml | 146 ++++++++++++++++++++++++++++ blueprints/mailu/instructions.md | 23 +++++ blueprints/mailu/mailu.svg | 137 ++++++++++++++++++++++++++ blueprints/mailu/meta.json | 21 ++++ blueprints/mailu/template.toml | 17 ++++ 5 files changed, 344 insertions(+) create mode 100644 blueprints/mailu/docker-compose.yml create mode 100644 blueprints/mailu/instructions.md create mode 100644 blueprints/mailu/mailu.svg create mode 100644 blueprints/mailu/meta.json create mode 100644 blueprints/mailu/template.toml diff --git a/blueprints/mailu/docker-compose.yml b/blueprints/mailu/docker-compose.yml new file mode 100644 index 00000000..3b644361 --- /dev/null +++ b/blueprints/mailu/docker-compose.yml @@ -0,0 +1,146 @@ +version: "3.8" + +# Shared Mailu configuration (equivalent to the upstream mailu.env file) +x-mailu-env: &mailu-env + - SECRET_KEY=${SECRET_KEY} + - DOMAIN=${DOMAIN} + - HOSTNAMES=${DOMAIN} + - POSTMASTER=admin + # TLS for the mail protocols is obtained through Let's Encrypt (HTTP-01 + # through Traefik on port 80). The web UI itself is served plain HTTP to + # Traefik, which terminates HTTPS. + - TLS_FLAVOR=mail-letsencrypt + # Mailu grants relay/XCLIENT trust to this network range. It must cover the + # Docker networks of this project (Docker allocates them from 172.16.0.0/12 + # by default). Narrow it down if you know your exact subnet. + - SUBNET=${SUBNET} + - ADMIN=true + - WEBMAIL=roundcube + - API=false + - WEBDAV=none + - ANTIVIRUS=none + - SCAN_MACROS=false + # Ports enabled inside the front container. 4190 (sieve) stays internal for + # the webmail filters UI and is not published on the host. + - PORTS=25,80,443,465,587,993,4190 + - MESSAGE_SIZE_LIMIT=50000000 + - FULL_TEXT_SEARCH=en + - WEBROOT_REDIRECT=/webmail + - WEB_ADMIN=/admin + - WEB_WEBMAIL=/webmail + - SITENAME=Mailu + - WEBSITE=https://mailu.io + # Trust Traefik (dokploy-network) to forward the real client IP for rate limiting + - REAL_IP_FROM=${SUBNET} + - REAL_IP_HEADER=X-Forwarded-For + # Initial admin account: admin@${DOMAIN} (created only if it does not exist) + - INITIAL_ADMIN_ACCOUNT=${INITIAL_ADMIN_ACCOUNT} + - INITIAL_ADMIN_DOMAIN=${DOMAIN} + - INITIAL_ADMIN_PW=${INITIAL_ADMIN_PW} + - INITIAL_ADMIN_MODE=ifmissing + +# Public DNSSEC-validating resolvers. The admin container refuses to start +# without DNSSEC validation, and Postfix needs it for outbound DANE. Service +# discovery still uses Docker's embedded DNS; these are only upstreams. +x-mailu-dns: &mailu-dns + - "1.1.1.1" + - "8.8.8.8" + +services: + front: + image: ghcr.io/mailu/nginx:2024.06 + restart: unless-stopped + environment: *mailu-env + dns: *mailu-dns + # Mail protocol ports are published directly on the host (same approach as + # the poste.io template). They will fail to bind if another mail server + # already uses them on this machine. + ports: + - "25:25" # SMTP (server to server) + - "465:465" # SMTPS submission + - "587:587" # Submission (STARTTLS) + - "993:993" # IMAPS + volumes: + - mailu-certs:/certs + # The image's built-in healthcheck also requires the mail (Dovecot) proxy, + # which only starts once TLS certificates exist. Behind Traefik that + # deadlocks: unhealthy -> Traefik drops the route -> the ACME challenge can + # never be answered. Check only nginx instead (health endpoint returns 204). + healthcheck: + test: ["CMD-SHELL", "curl -m3 -skfLo /dev/null http://127.0.0.1:10204/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 60s + depends_on: + - admin + + admin: + image: ghcr.io/mailu/admin:2024.06 + restart: unless-stopped + environment: *mailu-env + dns: *mailu-dns + volumes: + - mailu-data:/data + - mailu-dkim:/dkim + depends_on: + - redis + + imap: + image: ghcr.io/mailu/dovecot:2024.06 + restart: unless-stopped + environment: *mailu-env + dns: *mailu-dns + volumes: + - mailu-mail:/mail + depends_on: + - front + + smtp: + image: ghcr.io/mailu/postfix:2024.06 + restart: unless-stopped + environment: *mailu-env + dns: *mailu-dns + volumes: + - mailu-mailqueue:/queue + depends_on: + - front + + antispam: + image: ghcr.io/mailu/rspamd:2024.06 + restart: unless-stopped + hostname: antispam + environment: *mailu-env + dns: *mailu-dns + volumes: + - mailu-filter:/var/lib/rspamd + depends_on: + - front + - redis + + webmail: + image: ghcr.io/mailu/webmail:2024.06 + restart: unless-stopped + environment: *mailu-env + dns: *mailu-dns + volumes: + - mailu-webmail:/data + depends_on: + - front + - imap + + redis: + image: redis:alpine + restart: unless-stopped + volumes: + - mailu-redis:/data + +volumes: + mailu-certs: {} + mailu-data: {} + mailu-dkim: {} + mailu-mail: {} + mailu-mailqueue: {} + mailu-filter: {} + mailu-webmail: {} + mailu-redis: {} diff --git a/blueprints/mailu/instructions.md b/blueprints/mailu/instructions.md new file mode 100644 index 00000000..014883fe --- /dev/null +++ b/blueprints/mailu/instructions.md @@ -0,0 +1,23 @@ +# Mailu + +## Getting started + +1. Point the domain you assign in Dokploy (for example `mail.example.com`) at your server **before** deploying: an `A` record, plus an `MX` record for your mail domain targeting it. +2. Deploy the template and open the domain: `/webmail` is Roundcube, `/admin` is the admin UI. +3. Log in at `/admin` with `admin@` and the auto-generated `INITIAL_ADMIN_PW` (Dokploy → your service → Environment). The account is created only on first boot (`INITIAL_ADMIN_MODE=ifmissing`); change the password from the admin UI afterwards. +4. In the admin UI, open **Mail domains → your domain → Details** and create the DNS records it shows (SPF, DKIM, DMARC). Also set the **PTR/reverse DNS** record of your server IP to your mail hostname — most providers require this to accept your mail. + +## Ports + +The mail protocol ports are published directly on the host: **25** (SMTP), **465** (SMTPS), **587** (submission) and **993** (IMAPS). The deployment fails to start if another mail server (or a previous Mailu deployment) already binds them on the same machine. Many VPS providers block outbound port 25 by default — ask your provider to unblock it, or configure a relay host in Mailu. + +## TLS + +- The web UI is served through Traefik like any other Dokploy app. Enable **HTTPS with Let's Encrypt** on the Dokploy domain: the web login cookie requires HTTPS, and Mailu's internal certbot self-check follows Traefik's HTTP→HTTPS redirect and needs a valid certificate there. +- The mail ports get their own Let's Encrypt certificate: the `front` container runs certbot internally and answers the HTTP-01 challenge through Traefik on port 80. This only succeeds once the DNS record of your domain points at the server. If the certificate was obtained *after* the first boot, restart the `front` service once so the TLS mail listeners (465/587/993) come up. + +## Notes + +- `SUBNET` (default `172.16.0.0/12`) is the network range Mailu trusts for its internal traffic (Postfix relay/XCLIENT, Dovecot proxying, Rspamd). It covers Docker's default address pools; if your Docker daemon uses custom pools, adjust it to match. Note this means other containers on the same Docker host are treated as trusted senders. +- The containers use public DNSSEC-validating resolvers (`1.1.1.1`, `8.8.8.8`) as upstream DNS: the admin container requires DNSSEC validation and Postfix uses it for DANE. Heavy production use benefits from a dedicated local resolver instead, because public resolvers are rate-limited by DNSBLs (see the [Mailu DNS FAQ](https://mailu.io/2024.06/faq.html)). +- Additional mail domains, users, aliases and fetchmail can be managed in the admin UI. ClamAV antivirus is not included in this template to keep memory usage low (it needs >1 GB RAM); see the Mailu docs to add it. diff --git a/blueprints/mailu/mailu.svg b/blueprints/mailu/mailu.svg new file mode 100644 index 00000000..50374490 --- /dev/null +++ b/blueprints/mailu/mailu.svg @@ -0,0 +1,137 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + Mailu + + email since 2016 + + email since 2016 + delivering insular + + + + + diff --git a/blueprints/mailu/meta.json b/blueprints/mailu/meta.json new file mode 100644 index 00000000..3a41a263 --- /dev/null +++ b/blueprints/mailu/meta.json @@ -0,0 +1,21 @@ +{ + "id": "mailu", + "name": "Mailu", + "version": "2024.06", + "description": "Full-featured open-source mail server as a set of Docker containers: SMTP (Postfix), IMAP (Dovecot), Rspamd antispam, admin UI with DKIM management and Roundcube webmail.", + "logo": "mailu.svg", + "links": { + "github": "https://github.com/Mailu/Mailu", + "website": "https://mailu.io/", + "docs": "https://mailu.io/2024.06/", + "docker": "https://github.com/orgs/Mailu/packages" + }, + "tags": [ + "email", + "mail-server", + "smtp", + "imap", + "antispam", + "webmail" + ] +} diff --git a/blueprints/mailu/template.toml b/blueprints/mailu/template.toml new file mode 100644 index 00000000..acade041 --- /dev/null +++ b/blueprints/mailu/template.toml @@ -0,0 +1,17 @@ +[variables] +main_domain = "${domain}" +secret_key = "${password:16}" +admin_password = "${password:32}" + +[config] +[[config.domains]] +serviceName = "front" +port = 80 +host = "${main_domain}" + +[config.env] +DOMAIN = "${main_domain}" +SECRET_KEY = "${secret_key}" +INITIAL_ADMIN_ACCOUNT = "admin" +INITIAL_ADMIN_PW = "${admin_password}" +SUBNET = "172.16.0.0/12"