Mailu Docker Compose 部署指南

Mailu 2024.06.65 在 Ubuntu 22.04 的 Docker Compose 全链路部署指南:端口映射改造(8128-8132 段)、mailu.env 关键参数、初始管理员自动创建、SMTP(25)/SMTPS(465)/Submission(587)/IMAPS(993) 协议级收发实测与常见问题排障。

Mailu邮件服务器Docker Compose2026/10/21

Mailu Docker Compose 部署指南

目标:在 Ubuntu 22.04 服务器上以 Docker Compose 完整部署 Mailu 2024.06.65 邮件服务器套件(front/smtp/imap/antispam/admin/resolver/redis 七服务),完成初始管理员创建、邮件域与用户开通,并对 SMTP(25)/SMTPS(465)/Submission(587)/IMAPS(993) 做协议级收发实测。全流程已在 Ubuntu 22.04(x86_64)实测通过,共享主机环境使用 8128–8132 端口段映射(宿主经典邮件端口已被系统策略保留,映射细节见第八章)。

一、环境信息

  • 操作系统:Ubuntu 22.04(x86_64)
  • Docker:29.8.1,含 compose 插件(docker compose version → Docker Compose version v5.5.1)
  • Mailu:2024.06.65(2024.06 稳定分支最新锁定版,GitHub Releases 实查,发布日期 2026-10-20)
  • 部署目录:/opt/data/quzuo/tmp/mailu/compose/(编排文件与 mailu.env 同目录)
  • 数据目录:/opt/data/quzuo/tmp/mailu/data/(bind mount 持久化,见第五章)
  • 邮件主机名:mail.example.com;邮件主域:example.com(内网实验环境用 hosts/内网 DNS 解析,公网环境用 DNS A 记录 + MX 记录,见第三章)
  • 端口规划:宿主侧使用 8128–8132 五个端口映射邮件服务(8128→25、8129→465、8130→587、8131→993、8132→443),其余端口省略(详见第八章)
  • 服务栈网络:自定义网桥 10.89.56.0/24(Mailu 的 SUBNET 必须与其一致)
  • 初始管理员:admin@example.com,密码 q1w2e3r4(mailu.env 的 INITIAL_ADMIN_* 自动创建)
  • 部署前资源复核:
$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi       8.7Gi       1.6Gi        53Mi        20Gi        21Gi

官方最低要求为"不启用 ClamAV 时 1GB 内存 + 1GB 交换分区"(mailu.io/2024.06/compose/requirements.html);本部署 ANTIVIRUS=none,21GiB 可用余量充足。

二、镜像选型说明

Mailu 官方以多服务协作栈发行,每个服务角色由官方维护的独立服务单元承担,均在 GitHub Container Registry(ghcr.io)发布,仓库为 ghcr.io/mailu/<角色>。本部署的镜像清单与实测大小:

$ docker image ls --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' | grep -E 'mailu|redis' | sort
ghcr.io/mailu/admin:2024.06.65  362MB
ghcr.io/mailu/dovecot:2024.06.65        441MB
ghcr.io/mailu/nginx:2024.06.65  415MB
ghcr.io/mailu/postfix:2024.06.65        359MB
ghcr.io/mailu/rspamd:2024.06.65 432MB
ghcr.io/mailu/unbound:2024.06.65        358MB
redis:alpine    161MB

选型要点:

  • 锁定版 tag:官方同分支提供两种 tag——主版本号(2024.06,随分支滚动更新)与锁定版(2024.06.65,固定到某次发布)。生产与实验环境建议用锁定版,避免"重新拉起服务后行为悄然变化"。本文统一锁定 2024.06.65。
  • 角色与底层软件对照:nginx(front 统一入口)、postfix(smtp)、dovecot(imap)、rspamd(antispam)、admin(Flask 管理后台+API)、unbound(DNSSEC 递归解析,必选)、redis:alpine(缓存/限速计数,唯一非 ghcr.io/mailu 依赖)。
  • 本部署裁剪了可选项:Webmail(WEBMAIL=none)、ClamAV(ANTIVIRUS=none)、WebDAV(WEBDAV=none)、fetchmail(FETCHMAIL_ENABLED=False)——这些角色默认就不在启动集合里,开启方式见环境变量表。

⚠️ 注意:Mailu 2024.06 的 front 默认只暴露 25,80,443,465,993,995,4190(PORTS 默认值),Submission(587)/IMAP(143)/POP3(110) 需显式加进 PORTS 才会启用——本文第四章已处理,踩坑实录见第十章。

三、前置条件

3.1 基础软件

Docker ≥ 24.0 与 compose 插件:

$ docker version --format '{{.Server.Version}}'
29.8.1
$ docker compose version
Docker Compose version v5.5.1

3.2 域名解析

公网部署需要四类 DNS 记录:mail.example.com 的 A 记录指向服务器、example.com 的 MX 记录指向 mail.example.com、example.com 的 SPF TXT 记录(v=spf1 mx ~all)、DKIM 公钥 TXT 记录(部署后在管理台/API 读取,见本专栏《Mailu 2024.06.65 介绍与选型指南》与《Mailu 2024.06.65 配置参数详解》)。

内网/实验环境无公网 DNS,客户端侧用 hosts 映射等效实现(本文客户端为同网段一台 Ubuntu 22.04 客户机 192.168.146.0/24 网段):

# 客户机上执行(内容追加到 /etc/hosts)
192.168.146.128 mail.example.com

服务栈内部各角色互访使用服务名(front/smtp/imap/admin 等)自动解析,不依赖 hosts。

3.3 端口规划

本机为共享主机,经典邮件端口不可直接绑宿主,统一改用 8128–8132 端口段做映射。部署前先确认无占用:

$ ss -ltn | grep -E ':(8128|8129|8130|8131|8132)\b' || echo '端口段 8128-8132 空闲'
端口段 8128-8132 空闲

3.4 目录规划

$ mkdir -p /opt/data/quzuo/tmp/mailu/compose \
           /opt/data/quzuo/tmp/mailu/data/{certs,redis,mail,mailqueue,filter,dkim,main} \
           /opt/data/quzuo/tmp/mailu/data/overrides/{nginx,dovecot,postfix,rspamd}
$ cd /opt/data/quzuo/tmp/mailu/compose

四、部署步骤

4.1 准备 TLS 证书(TLS_FLAVOR=cert)

Mailu 的 TLS 策略由 mailu.env 的 TLS_FLAVOR 决定(可取 letsencrypt/mail-letsencrypt/cert/mail/notls)。本文采用 cert(自带证书)模式:front 从 /certs 目录读取 cert.pem/key.pem。实验环境用自签证书模拟;公网生产可直接放置 Let's Encrypt 或商业证书的对应文件。

$ cd /opt/data/quzuo/tmp/mailu/data/certs
$ openssl req -new -x509 -days 3650 -nodes \
    -out cert.pem -keyout key.pem \
    -subj "/CN=mail.example.com/O=Mailu Demo" \
    -addext "subjectAltName=DNS:mail.example.com,IP:127.0.0.1"
$ openssl x509 -in cert.pem -noout -subject -ext subjectAltName
subject=CN = mail.example.com, O = Mailu Demo
X509v3 Subject Alternative Name:
    DNS:mail.example.com, IP Address:127.0.0.1

证书文件名必须是 cert.pem/key.pem(可用 TLS_CERT_FILENAME/TLS_KEYPAIR_FILENAME 改名);私钥权限收紧为 600:

$ chmod 600 key.pem && ls -l
total 8
-rw-r--r-- 1 root root 1237 10月 21 08:39 cert.pem
-rw------- 1 root root 1704 10月 21 08:39 key.pem

4.2 编写 mailu.env

在 /opt/data/quzuo/tmp/mailu/compose/mailu.env 写入以下内容(基于官方 setup 向导生成模板,逐项说明见代码块后注释;参数全集详解见本专栏《Mailu 2024.06.65 配置参数详解》):

# ============ 通用 ============
# 16 字节随机串,每次部署必须唯一(可用 openssl rand -base64 12 生成)
SECRET_KEY=5ku+ABoBFSidF23i
# 服务栈网段,必须与编排文件里的网桥子网一致
SUBNET=10.89.56.0/24
# 主邮件域(生成 postmaster 等技术地址、退信发件人)
DOMAIN=example.com
# 对外主机名,逗号分隔,第一个为主主机名(证书 CN 也要覆盖它)
HOSTNAMES=mail.example.com
# postmaster 的本地部分
POSTMASTER=admin
# TLS 策略:letsencrypt/mail-letsencrypt/cert/mail/notls
TLS_FLAVOR=cert
# 认证限速:按 IP 段 / 按账号
AUTH_RATELIMIT_IP=60/hour
AUTH_RATELIMIT_USER=100/day
# 关闭匿名统计上报
DISABLE_STATISTICS=True

# ============ 可选功能 ============
ADMIN=true
WEBMAIL=none
API=true
WEBDAV=none
ANTIVIRUS=none
SCAN_MACROS=false

# ============ 邮件设置 ============
MESSAGE_SIZE_LIMIT=50000000
RELAYNETS=
RELAYHOST=
FETCHMAIL_ENABLED=False
FETCHMAIL_DELAY=600
RECIPIENT_DELIMITER=+
DMARC_RUA=admin
DMARC_RUF=admin
WELCOME=false
WELCOME_SUBJECT=Welcome to your new email account
WELCOME_BODY=Welcome to your new email account, if you can read this, then it is configured properly!
COMPRESSION=
COMPRESSION_LEVEL=6
FULL_TEXT_SEARCH=en
# front 对外启用的邮件端口清单;默认 25,80,443,465,993,995,4190,
# Submission(587)/IMAP(143)/POP3(110) 必须显式加入
PORTS=25,80,443,465,587,993,995,4190

# ============ Web 设置 ============
WEBROOT_REDIRECT=/admin
WEB_ADMIN=/admin
WEB_WEBMAIL=/webmail
WEB_API=/api
SITENAME=Mailu
WEBSITE=https://mail.example.com

# ============ 高级设置 ============
COMPOSE_PROJECT_NAME=mailu-2024.06.65-stack
CREDENTIAL_ROUNDS=12
REAL_IP_HEADER=
REAL_IP_FROM=
REJECT_UNLISTED_RECIPIENT=yes
LOG_LEVEL=INFO
TZ=Asia/Shanghai
DEFAULT_SPAM_THRESHOLD=80
# RESTful API 令牌(最短 3 字符),API=true 时必配
API_TOKEN=q1w2e3r4
# 首启自动创建管理员:create/ifmissing/update 三种模式
INITIAL_ADMIN_ACCOUNT=admin
INITIAL_ADMIN_DOMAIN=example.com
INITIAL_ADMIN_PW=q1w2e3r4
INITIAL_ADMIN_MODE=ifmissing
FULL_TEXT_SEARCH_ATTACHMENTS=false

关键项解读:

参数取值作用与影响
SECRET_KEY16 字节随机串会话 Cookie 签名等关键加密用途,更换后所有登录态失效
SUBNET10.89.56.0/24必须等于编排文件网桥子网,否则各角色互访异常
TLS_FLAVORcert从 /certs 读自带证书;若公网想自动签发改 letsencrypt
PORTS含 587不加 587 则 Submission 端口根本不监听(默认值不含,本文实测踩坑,见第十章)
INITIAL_ADMIN_MODEifmissing已存在则跳过(重启安全);create 重启会报错,官方建议 ifmissing/update
API_TOKENq1w2e3r4启用 RESTful API 的 Bearer 令牌
TZAsia/Shanghai服务栈内时区,日志时间戳随之

4.3 编写 docker-compose.yml

在 /opt/data/quzuo/tmp/mailu/compose/docker-compose.yml 写入以下内容(在官方 setup 向导生成模板基础上做了三处本地化:显式项目名、网段 10.89.56.0/24、front 端口全部改为映射到 8128–8132 段):

name: mailu-2024.06.65-stack

services:

  redis:
    image: redis:alpine
    restart: always
    volumes:
      - "/opt/data/quzuo/tmp/mailu/data/redis:/data"
    depends_on:
      - resolver
    dns:
      - 10.89.56.254

  front:
    image: ghcr.io/mailu/nginx:2024.06.65
    restart: always
    env_file: mailu.env
    logging:
      driver: journald
      options:
        tag: mailu-front
    ports:
      - "8128:25"
      - "8129:465"
      - "8130:587"
      - "8131:993"
      - "8132:443"
    networks:
      - default
    volumes:
      - "/opt/data/quzuo/tmp/mailu/data/certs:/certs"
      - "/opt/data/quzuo/tmp/mailu/data/overrides/nginx:/overrides:ro"
    depends_on:
      - resolver
    dns:
      - 10.89.56.254

  resolver:
    image: ghcr.io/mailu/unbound:2024.06.65
    env_file: mailu.env
    logging:
      driver: journald
      options:
        tag: mailu-resolver
    restart: always
    networks:
      default:
        ipv4_address: 10.89.56.254

  admin:
    image: ghcr.io/mailu/admin:2024.06.65
    restart: always
    env_file: mailu.env
    logging:
      driver: journald
      options:
        tag: mailu-admin
    volumes:
      - "/opt/data/quzuo/tmp/mailu/data/main:/data"
      - "/opt/data/quzuo/tmp/mailu/data/dkim:/dkim"
    depends_on:
      - redis
      - resolver
    dns:
      - 10.89.56.254

  imap:
    image: ghcr.io/mailu/dovecot:2024.06.65
    restart: always
    env_file: mailu.env
    logging:
      driver: journald
      options:
        tag: mailu-imap
    volumes:
      - "/opt/data/quzuo/tmp/mailu/data/mail:/mail"
      - "/opt/data/quzuo/tmp/mailu/data/overrides/dovecot:/overrides:ro"
    networks:
      - default
    depends_on:
      - front
      - resolver
    dns:
      - 10.89.56.254

  smtp:
    image: ghcr.io/mailu/postfix:2024.06.65
    restart: always
    env_file: mailu.env
    logging:
      driver: journald
      options:
        tag: mailu-smtp
    volumes:
      - "/opt/data/quzuo/tmp/mailu/data/mailqueue:/queue"
      - "/opt/data/quzuo/tmp/mailu/data/overrides/postfix:/overrides:ro"
    depends_on:
      - front
      - resolver
    dns:
      - 10.89.56.254

  antispam:
    image: ghcr.io/mailu/rspamd:2024.06.65
    hostname: antispam
    restart: always
    env_file: mailu.env
    logging:
      driver: journald
      options:
        tag: mailu-antispam
    networks:
      - default
    volumes:
      - "/opt/data/quzuo/tmp/mailu/data/filter:/var/lib/rspamd"
      - "/opt/data/quzuo/tmp/mailu/data/overrides/rspamd:/overrides:ro"
    depends_on:
      - front
      - redis
      - resolver
    dns:
      - 10.89.56.254

networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 10.89.56.0/24

配置要点逐项说明:

  • name: mailu-2024.06.65-stack:显式服务栈项目名(版本号中的点号会被 Docker 规范化,实际运行时项目内资源名显示为 mailu-20240665-stack-*)。共享主机上多个编排项目并存时,显式项目名可避免同名项目互相干扰。
  • resolver 固定 IP 10.89.56.254:Mailu 强制所有角色用内建 Unbound 做 DNSSEC 解析,dns: 10.89.56.254 把各角色的 DNS 指向它;该 IP 必须在 SUBNET 网段内。
  • 端口映射 8128-8132:官方模板把 10 个端口直接绑宿主,此处改为 5 条映射(选择理由与省略清单见第八章)。
  • env_file: mailu.env:除 resolver 外每个角色都注入同一份环境配置;resolver 也读 mailu.env(其转发行为受配置影响)。
  • 数据卷:全部 bind mount 到 /opt/data/quzuo/tmp/mailu/data/ 子目录,映射关系见第五章。

4.4 启动服务栈

$ cd /opt/data/quzuo/tmp/mailu/compose
$ docker compose up -d
 Container mailu-20240665-stack-resolver-1 Starting
 Container mailu-20240665-stack-resolver-1 Started
 Container mailu-20240665-stack-front-1 Starting
 Container mailu-20240665-stack-redis-1 Starting
 Container mailu-20240665-stack-front-1 Started
 Container mailu-20240665-stack-imap-1 Starting
 Container mailu-20240665-stack-smtp-1 Starting
 Container mailu-20240665-stack-redis-1 Started
 Container mailu-20240665-stack-antispam-1 Starting
 Container mailu-20240665-stack-admin-1 Starting
 Container mailu-20240665-stack-imap-1 Started
 Container mailu-20240665-stack-smtp-1 Started
 Container mailu-20240665-stack-antispam-1 Started
 Container mailu-20240665-stack-admin-1 Started

💡 首次执行会先自动拉取 7 个服务单元(约 2.5GB),拉取完成后逐个启动。启动顺序由依赖关系决定:resolver 最先,admin 最后。

等待半分钟左右,确认七服务全部就绪(admin/imap/resolver 需完成健康检查,从 health: starting 变为 healthy):

$ docker compose ps --format 'table {{.Service}}\t{{.Status}}'
SERVICE    STATUS
admin      Up 28 seconds (health: starting)
antispam   Up 29 seconds (healthy)
front      Up 29 seconds (healthy)
imap       Up 29 seconds (health: starting)
redis      Up 29 seconds
resolver   Up 29 seconds (health: starting)
smtp       Up 29 seconds (healthy)

⚠️ 注意:admin 的健康检查内含 DNSSEC 强制自检(解析 example.org 验证 AD 标志位),若内建 resolver 异常,admin 将拒绝启动并在日志里输出 "Your DNS resolver isn't doing DNSSEC validation"。这是官方反 DNS 欺骗的刻意设计,不要试图绕过,排查 resolver 即可。

4.5 验证初始管理员自动创建

admin 首启会按 INITIAL_ADMIN_* 变量创建全局管理员(domain 也会随之自动创建):

$ docker compose logs admin 2>&1 | grep -i 'creating initial'
admin-1  | INFO:root:Creating initial admin account admin@example.com with mode ifmissing

用导出命令核对账号与域已落库(导出命令详解见本专栏《Mailu 2024.06.65 常用 CLI 命令详解》):

$ docker compose exec -T admin flask mailu config-export user.email user.global_admin
user:
  - email: admin@example.com
    global_admin: true
  - email: user1@example.com
    global_admin: false

(user1 为后续步骤创建的测试账号;全新部署此处只有 admin 一条。)

4.6 验证 Web 管理台

$ curl -sk -o /dev/null -w '%{http_code} -> %{redirect_url}\n' https://127.0.0.1:8132/
308 -> https://127.0.0.1:8132/admin/?homepage
$ curl -skL -o /dev/null -w 'admin UI final: %{http_code} %{url_effective}\n' https://127.0.0.1:8132/admin/
admin UI final: 200 https://127.0.0.1:8132/sso/login?url=https://127.0.0.1:8132/admin/

根路径 308 跳转到管理台,未登录最终落在统一登录页 /sso/login(HTTP 200)。浏览器访问 https://mail.example.com:8132/admin/(或 https://192.168.146.128:8132/admin/),用 admin@example.com / q1w2e3r4 登录即可进入管理界面。自签证书会触发浏览器告警,属预期行为(换正式证书即消除)。

4.7 创建邮件用户

用官方管理命令行创建普通用户 user1@example.com(成功时静默无输出):

$ docker compose exec -T admin flask mailu user user1 example.com 'q1w2e3r4'
$ docker compose exec -T admin flask mailu config-export user.email user.enabled
user:
  - email: admin@example.com
    enabled: true
  - email: user1@example.com
    enabled: true

4.8 验证宿主端口监听

$ ss -ltn | grep -E ':(8128|8129|8130|8131|8132)\b'
LISTEN 0      4096         0.0.0.0:8132       0.0.0.0:*
LISTEN 0      4096         0.0.0.0:8128       0.0.0.0:*
LISTEN 0      4096         0.0.0.0:8129       0.0.0.0:*
LISTEN 0      4096         0.0.0.0:8130       0.0.0.0:*
LISTEN 0      4096         0.0.0.0:8131       0.0.0.0:*

五个宿主端口全部就绪。

4.9 SMTP/IMAP 协议级实测

① 465 SMTPS 握手(服务器本机,-quiet 抑制证书告警噪音):

$ (printf 'EHLO test.example.org\r\nQUIT\r\n'; sleep 2) | \
    timeout 8 openssl s_client -connect 127.0.0.1:8129 -quiet 2>&1 | head -12
Can't use SSL_get_servername
depth=0 CN = mail.example.com, O = Mailu Demo
verify error:num=18:self-signed certificate
verify return:1
depth=0 CN = mail.example.com, O = Mailu Demo
verify return:1
220 mail.example.com Dovecot ready.
250-mail.example.com
250-8BITMIME
250-AUTH PLAIN LOGIN
250-BURL imap
250-CHUNKING

TLS 握手成功,证书即第 4.1 步的自签证书(CN=mail.example.com),对端自称 mail.example.com Dovecot ready。

② 25 端口入站投递(客户端机向 user1@example.com 投一封测试邮件):

$ swaks --from admin@example.com --to user1@example.com \
    --server mail.example.com:8128 --h-Subject 'B2 inbound test'
<-  250-ENHANCEDSTATUSCODES
<-  250-8BITMIME
<-  250-DSN
<-  250 STARTTLS
 -> MAIL FROM:<admin@example.com>
<-  250 2.0.0 OK
 -> RCPT TO:<user1@example.com>
<-  250 2.1.5 Ok
 -> DATA
<-  354 End data with <CR><LF>.<CR><LF>
 -> Date: Wed, 21 Oct 2026 00:43:04 +0000
 -> To: user1@example.com
 -> From: admin@example.com
 -> Subject: B2 inbound test
 -> Message-Id: <20261021004304.000416@64e0fe571313>
 -> X-Mailer: swaks v20201014.0 jetmore.org/john/code/swaks/
 ->
 -> This is a test mailing
 ->
 ->
 -> .
<-  250 2.0.0 Ok: queued as 21BD58C227B
 -> QUIT
<-  221 2.0.0 Bye
=== Connection closed with remote host.

入站邮件被队列接受(queued as 21BD58C227B)。

③ 587 认证提交 + STARTTLS(user1 向 admin 发信,验证认证与加密通道):

$ swaks --from user1@example.com --to admin@example.com \
    --server mail.example.com:8130 \
    --auth PLAIN --auth-user user1@example.com --auth-password q1w2e3r4 --tls
<~  220 mail.example.com Dovecot ready.
<~  250-mail.example.com
<~  250-8BITMIME
<~  250-SIZE 50000000
<~  250-STARTTLS
<~  220 2.0.0 Begin TLS negotiation now.
(TLS 协商成功,后续为密文会话)
 ~> RCPT TO:<admin@example.com>
<~  250 2.1.5 Ok
 ~> DATA
<~  354 End data with <CR><LF>.<CR><LF>
 ~> .
<~  250 2.0.0 Ok: queued as E012B8C22CE
 ~> QUIT
<~  221 2.0.0 Bye

注意回显 250-SIZE 50000000 正是 MESSAGE_SIZE_LIMIT 生效值;--auth PLAIN 在 STARTTLS 之后再发起,明文口令不出网。

④ 993 IMAPS 登录读信(服务器本机验证 user1 收到了第②步的邮件):

$ (printf 'a1 LOGIN user1@example.com q1w2e3r4\r\na2 SELECT INBOX\r\na3 FETCH 1 (BODY[HEADER.FIELDS (FROM SUBJECT)])\r\na4 LOGOUT\r\n'; sleep 3) | \
    timeout 10 openssl s_client -connect 127.0.0.1:8131 -quiet 2>/dev/null
* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN AUTH=LOGIN] Dovecot ready.
a1 OK [CAPABILITY IMAP4rev1 ... QUOTA] Logged in
* 1 EXISTS
* 1 RECENT
* OK [UNSEEN 1] First unseen.
a2 OK [READ-WRITE] Select completed (0.001 + 0.000 secs).
* 1 FETCH (FLAGS (\Seen \Recent) BODY[HEADER.FIELDS (FROM SUBJECT)] {53}
From: admin@example.com
Subject: B2 inbound test
)

1 EXISTS + FETCH 命中 From: admin@example.com / Subject: B2 inbound test——25 端口入站 → Dovecot 落箱 → 993 可读,收信链路闭环。再验证 admin 的收件箱(第③步那封):

$ (printf 'b1 LOGIN admin@example.com q1w2e3r4\r\nb2 SELECT INBOX\r\nb3 FETCH 1 (BODY[HEADER.FIELDS (FROM SUBJECT)])\r\nb4 LOGOUT\r\n'; sleep 3) | \
    timeout 10 openssl s_client -connect 127.0.0.1:8131 -quiet 2>/dev/null | grep -E '^(b[0-9]|From:|Subject:|\* 1)'
b1 OK [CAPABILITY IMAP4rev1 ... QUOTA] Logged in
* 1 FETCH (FLAGS (\Seen \Recent) BODY[HEADER.FIELDS (FROM SUBJECT)] {74}
From: user1@example.com
Subject: test Wed, 21 Oct 2026 00:44:20 +0000
b3 OK Fetch completed (0.001 + 0.000 secs).
b4 OK Logout completed (0.001 + 0.000 secs.)

双向收发全部闭环。

五、数据持久化说明

全栈数据用 bind mount 落在 /opt/data/quzuo/tmp/mailu/data/,重启/重建服务不丢:

宿主目录服务内挂载点内容
data/mainadmin 的 /data管理数据库(SQLite)、DKIM 密钥索引、webmail 数据
data/dkimadmin 的 /dkimDKIM 签名私钥
data/mailimap 的 /mail全部用户 Maildir 邮件本体
data/mailqueuesmtp 的 /queuePostfix 出站队列
data/filterantispam 的 /var/lib/rspamdRspamd 学习数据与统计
data/certsfront 的 /certsTLS 证书/私钥(cert.pem/key.pem)
data/redisredis 的 /data缓存与限速计数
data/overrides/*各服务 /overrides(只读)官方预留的配置覆盖目录(见第七章)

备份即打包:tar czf mailu-backup.tar.gz /opt/data/quzuo/tmp/mailu/data/(最好先 docker compose stop 再备,保证 Maildir 与 SQLite 一致性)。

六、环境变量表

mailu.env 全部变量以官方配置参考为准,上表之外的常用运维向变量:

变量默认值说明
PORTS25,80,443,465,993,995,4190front 启用的协议端口清单;25/465/993 不可禁用
MESSAGE_RATELIMIT200/day单用户日发信上限(防被盗号后狂发)
DEFAULT_QUOTA未设置域未配置配额时的新用户默认配额
ANTIVIRUS_ACTIONdiscard查出病毒是丢弃(reject 则 SMTP 阶段拒收)
SESSION_TIMEOUT3600管理台会话空闲超时(秒)
PERMANENT_SESSION_LIFETIME108000会话最长存活(秒)
LETSENCRYPT_SHORTCHAINFalseLet's Encrypt 证书链兼容老安卓开关
SQLALCHEMY_DATABASE_URIsqlite:////data/main.db管理库连接(官方不建议换库)

逐参数深度解析见本专栏《Mailu 2024.06.65 配置参数详解》。

七、配置文件挂载

各服务的 /overrides 目录(只读挂载)是官方预留的配置覆盖通道:在宿主 data/overrides/<服务>/ 放同名配置文件,即可覆盖服务内默认配置而无需改服务单元内容。常用场景:

  • overrides/postfix/main.cf:追加 Postfix 自定义主配置(如额外 smtpd 规则);
  • overrides/dovecot/conf.d/*.conf:Dovecot 自定义(如邮箱目录结构微调);
  • overrides/nginx/*.conf:front 的 Nginx 片段(如自定义 location);
  • overrides/rspamd/*.conf:Rspamd 模块级覆盖。

改动 overrides 后需要重建对应服务(docker compose up -d --force-recreate <服务名>)才能生效。

八、端口映射与省略说明

官方 setup 向导生成的编排把 front 的 10 个端口全部绑到宿主:25, 80, 443, 465, 110, 995, 143, 993, 4190。本部署运行在共享主机上,经典邮件端口不可直接绑宿主,故改为端口映射形态,从预分配的 8128–8132 段取 5 个端口:

宿主端口服务内端口协议是否映射说明
812825SMTP✔入站邮件主通道
8129465SMTPS(隐式 TLS)✔客户端加密提交
8130587Submission(STARTTLS)✔客户端提交标准口(需 PORTS 显式含 587)
8131993IMAPS(隐式 TLS)✔邮件客户端读取
8132443HTTPS✔管理台/API 入口
—80HTTP✘ 省略管理台走 443 已够用;80 主要服务 Let's Encrypt HTTP-01 校验(letsencrypt 模式才需要)
—110/995POP3/POP3S✘ 省略现代客户端基本走 IMAP;需要时把映射加回即可
—143IMAP(明文+STARTTLS)✘ 省略已映射 993(隐式 TLS),更安全
—4190Managesieve✘ 省略桌面客户端管理 Sieve 过滤规则用,管理台可代管

取舍原则:保留"能完整闭环收发+管理"的最小集合。客户端侧配置示例(以 Thunderbird 类客户端为参照):收件服务器 mail.example.com:8131(IMAPS),发件服务器 mail.example.com:8130(STARTTLS)或 mail.example.com:8129(SSL/TLS)。公网生产部署建议把 25/465/587/993/443 直接绑宿主(官方默认形态),并保留 80 供证书自动续期。

九、常用运维命令

以下命令均在部署目录 /opt/data/quzuo/tmp/mailu/compose/ 下执行:

# 查看服务状态
$ docker compose ps
# 查看某服务日志(journald tag 均已打上 mailu-<角色> 标签)
$ docker compose logs --tail 50 admin
# 跟随 antispam 日志
$ docker compose logs -f antispam
# 手动拉取新版并滚动升级(锁定版部署时,把两份文件里的 2024.06.65 改成新锁定版号)
$ docker compose pull && docker compose up -d
# 重启单个服务
$ docker compose restart front
# 重建某服务(改了 overrides 或环境变量后)
$ docker compose up -d --force-recreate admin
# 停止 / 彻底移除(数据在 bind mount,down 不删数据)
$ docker compose down

升级流程小结:改镜像 tag → docker compose pull → docker compose up -d(自动滚动重建受影响服务);回滚同理把 tag 改回旧锁定版即可。

十、常见问题

Q1:587 端口连接被拒(Connection refused),25/465 却正常?

实测踩坑记录:部署完成后从客户端提交报错:

*** Error connecting to mail.example.com:8130:
***     IO::Socket::INET6: connect: Connection refused

定位:ss -ltn 宿主侧 8130 明明在监听,但服务内 587 没开。原因:2024.06 的 PORTS 默认值是 25,80,443,465,993,995,4190,不含 587/143/110(官方配置参考原话:"If you need to re-enable IMAP, POP3 and Submission, you can append '110,143,587' to that list")。解决:在 mailu.env 加 PORTS=25,80,443,465,587,993,995,4190,然后 docker compose up -d 重建服务,再测即通。

Q2:admin 服务反复重启,日志报 DNS resolver 相关 critical?

admin 启动自检强制要求 DNSSEC 校验通过(解析 example.org 检查 AD 标志)。若宿主防火墙拦了出站 DNS 或 resolver 未就绪,admin 会一直报 "Your DNS resolver isn't doing DNSSEC validation" 并拒绝启动。排查 docker compose logs resolver,确认出站 53 端口(TCP/UDP)可达;本实验环境 resolver 就绪后 admin 即转 healthy。

Q3:浏览器/openssl 报 self-signed certificate 告警?

TLS_FLAVOR=cert 只负责"使用你提供的证书",不校验其来源;自签证书必然触发告警。生产环境换成 Let's Encrypt(TLS_FLAVOR=letsencrypt,需要 80/443 对公网开放)或正规商业证书(保持 cert 模式、替换 cert.pem/key.pem,front 会自动热加载——内建 certwatcher 监听证书文件变化)。

Q4:改了 mailu.env 不生效?

env_file 注入发生在服务重建时,改完必须 docker compose up -d(会自动重建受影响服务)或 up -d --force-recreate。仅 restart 不会重新读取环境文件。

Q5:命令行建的域没有 DKIM 密钥?

flask mailu domain / flask mailu admin 建域时不生成 DKIM 密钥(管理台 UI 建域才会生成)。补救:管理台进入该域点"重新生成密钥",或调 REST API POST /api/v1/domain/<域名>/dkim。

十一、参考链接