简介:Mailcow是一套基于Docker容器化技术构建的开源邮件服务器解决方案,适合需要自建邮件系统的运维工程师、中小企业IT管理员,以及希望通过私有化部署掌控数据安全和业务弹性的技术团队。压缩包共2000个文件、约10.98MB,主体为1515个PHP业务代码文件,辅以78个Markdown文档、61个JSON和19个YAML配置、54个Shell部署/维护脚本、15个Dockerfile,以及证书密钥示例、Sieve过滤规则、日志聚合与监控组件文件,能够清晰反映容器化邮件服务在反垃圾、反病毒、DKIM、DMARC、SPF等安全机制上的落地方式。读者既可按目录结构学习邮件网关、Webmail、反垃圾模块各自的职责边界,也可直接参考其中的Compose编排和配置模板,用于实际部署、二次开发或故障排查。目前已有202人学习下载,适合作为邮件服务私有化、Docker容器编排及邮件安全策略配置的入门与进阶资料。资源中还包含Syslog-ng日志处理、Carbon监控和Nextcloud协同配置等扩展内容,可帮读者串联起邮件服务器周边的完整服务链路。
1. 邮件服务器这事,凭什么轮到 Mailcow
如果你手动搭过 Postfix 加 Dovecot,大概能体会那种感觉:配置文件翻来覆去改,日志里全是看不明白的报错,发出去的邮件不是进垃圾箱就是被对方直接退信。某开发者 A 同学为了给团队配一个能用的邮件系统,照着几篇教程折腾了三天,最后被 SPF 和 DKIM 的记录格式卡住,实在受不了才换了 Mailcow——一个功能丰富的开源邮件服务器解决方案。它用 Docker 把 Postfix、Dovecot、MySQL、Redis、Rspamd、SOGo 这些组件打包成一个整体,装完之后自带网页邮箱、反垃圾、病毒查杀、多域名管理,省掉了单点维护的成本。这篇文章就是照着我在真实部署里验证过的路径,讲清楚它适合谁、怎么装、参数怎么调,以及哪些地方最容易翻车。
2. 装 Mailcow 之前的硬准备:DNS、端口与硬件选型
很多人把 Mailcow 部署失败归咎于安装命令,其实十个里有八个是栽在 DNS 和系统资源上。邮件服务对 DNS 的依赖比 Web 服务严格得多,域名解析不对,后面所有步骤都白搭。
2.1 MX 与 A 记录:一封邮件进来之前先过三关
邮件系统运转的第一步不是启动容器,而是让全世界都知道“这个域的邮件归谁收”。外部服务器发信到example.com时,会先查 MX 记录,拿到负责收信的服务器主机名;再通过 A 记录把这个主机名解析成具体 IP;连接 25 端口之后,对方还会反向查询这个 IP 的 PTR 记录,确认服务器身份。这三关少了任何一环,对方就可能拒收或者把信标记成垃圾。
我用一个独立的子域mail.example.com作为 Mailcow 的访问地址和 MX 目标,而不是直接用example.com。这样做的好处是,后期如果要把邮件服务迁移到另一台服务器,只需要改mail.example.com的 A 记录和 MX 指向,主域名保持不动,对发信方更友好。DNS 里需要准备的内容如下:
| 记录类型 | 主机名 | 值 |
|---|---|---|
| A | 服务器公网 IP | |
| MX | @ | mail.example.com(优先级 10) |
| PTR | 服务器 IP 反解 | mail.example.com |
| TXT | @ | 稍后配置 SPF |
反解记录一般得在服务器提供商的后台单独提交工单,这一步经常被忽略。验证解析是否生效,我在服务器上直接跑dig:
dig MX example.com dig A mail.example.comdig MX的输出里应该能看到mail.example.com的地址;dig A返回的 IP 必须和当前服务器公网 IP 完全一致。注意某些 DNS 服务商对 MX 记录的值会自动补域名后缀,填mail.example.com有时会被改写成mail.example.com.example.com,提交完之后一定要用dig复查一遍,这属于典型的玄学坑。
2.2 硬件与系统要求:哪些配置会让 Mailcow 带不动
Mailcow 官方给的基线是 2 核 4GB 内存,这确实能跑起来,但前提是你得清楚瓶颈在哪。我自己部署的第二台服务器就是 4GB 内存,跑了 80 个活跃邮箱,每天大概几百封进出信,平时占用在 70% 左右。如果开启 ClamAV 全量病毒扫描,邮件高峰时内存会明显吃紧,建议至少 8GB,否则系统会频繁触发 OOM,把 MySQL 或 Redis 容器杀掉,表现就是后台突然打不开、用户收不到信。
磁盘这块比内存更容易被低估。邮件属于写入型负载,单封邮件虽然只有几十 KB,但长期积累下来,几万个邮件加上索引和日志,很容易占掉几十 GB。我把/var/lib/docker单独挂到一块数据盘上,系统盘只放操作系统和 Docker 程序,避免日志把系统盘写满。还要留一部分 swap 空间,Mailcow 里不少组件是 Java 或内存敏感型进程,物理内存不足时 swap 能顶一阵,不至于立刻崩溃。
Docker 环境本身要求版本够新。Mailcow 新版依赖 Docker Compose v2,老机器上如果装的是docker-compose独立二进制和很老的 Docker Engine,启动时会出现字段不兼容的报错。先执行docker --version确认版本较新,再开始部署,能省掉后面不少排查时间。
3. 用 Docker Compose 把 Mailcow 拉起来:最小可运行命令
硬件和 DNS 准备好之后,实际安装过程反而比想象中简单。整个部署可以拆成三步:克隆代码、生成配置、拉镜像启动。每一步都有关键参数要确认,顺序不能乱。
3.1 克隆与生成配置:域名、时区与随机密码
先在服务器上把 Mailcow 的官方编排仓库克隆下来,然后进入目录执行配置生成脚本:
git clone https://github.com/mailcow/mailcow-dockerized cd mailcow-dockerized ./generate_config.sh这个脚本会以交互方式问你几个问题:邮件服务器主机名(我填mail.example.com)、时区(我填Asia/Shanghai)。回答完之后,脚本会在当前目录生成一份mailcow.conf,里面包含了数据库密码、API 密钥、管理员初始密码等敏感信息。注意这些密码是随机生成的,不是让你自己设一个统一密码。
generate_config.sh生成的配置文件是整个系统的核心。MAILCOW_HOSTNAME决定了 Nginx 对外服务的域名,如果这里填错了,后面改起来要连带改数据库里的配置。脚本完成后,立刻把mailcow.conf备份到服务器之外,别等到部署完再备份——里面有数据库连接密码,丢了之后容器启动就会连不上库。
3.2 拉镜像与启动:先拉后起与健康检查
镜像拉取是整个部署里最耗时的一步,建议先单独执行拉取,再启动容器:
docker compose pull docker compose up -d为什么不直接docker compose up -d让它自己拉?因为 Mailcow 涉及的镜像有二十个左右,加起来好几个 GB,直接启动时如果网络波动导致某个镜像拉取失败,Compose 会留下半截状态,重试时进度判断很麻烦。分开执行的好处是拉取阶段能看清每个镜像的状态,失败时可以单独重试。
启动完成后,用docker compose ps检查容器状态:
docker compose ps正常情况下,所有服务的 STATUS 列应该是Up或(healthy)。如果某个组件一直卡在starting或反复重启,说明配置或环境有问题,先看对应容器的日志再往下走。我第一次安装时dovecot-mailcow一直起不来,查日志发现是端口和现有服务冲突,改完端口后正常。
3.3 首次打开后台:管理员入口与证书等待期
容器全部起来之后,浏览器访问https://mail.example.com。此时浏览器会提示证书不可信,这是正常的,因为 Mailcow 的 ACME 客户端要等域名解析生效后才会自动向证书机构申请证书。等 2 到 5 分钟再刷新,证书就会变成有效状态。
登录界面用默认管理员账号admin,密码在刚才执行generate_config.sh时打印在终端里。如果当时没记住,可以直接查看mailcow.conf里生成的初始密码字段,或者用脚本提供的方式重置。登录后台之后第一件事不是急着加邮箱,而是先确认系统检查页有没有红色警告项,比如 DNS 解析没生效、端口被占用、磁盘空间不足等。后台会把这些问题标出来,趁环境还没复杂化时处理掉,后面运维会轻松很多。
4. 域名入池之后:DKIM、SPF、DMARC 与反垃圾参数
Mailcow 装上只是第一步,真正决定邮件能不能顺利投递的,是 DNS 里的三条 TXT 记录和反垃圾策略。很多团队在这步草草了事,结果发出去的信全部被人标记成垃圾邮件。
4.1 添加域名与邮箱:后台里最关键的三个开关
后台左侧菜单找到 System 下的域名管理,点“添加域名”,填example.com,勾选“已准备好接收该域名的邮件”和“启用此域”。第一个选项含义是确认 MX 记录已经指向当前服务器,勾上之后 Mailcow 才接受投递到这个域的信件。第二个选项控制该域是否允许通过 Webmail 或 SMTP 发信。
域名添加成功后,再创建邮箱账号。邮箱名、密码、配额按需填,配额我建议从 1GB 起步,并给管理员账号单独留一个配额用于存放系统通知邮件。添加完之后,可以在“编辑”菜单里给邮箱配置别名和转发规则。
需要注意的是,Mailcow 默认会把新域名的主机名设置成mail.example.com,这个值要和你在 DNS 里配置的 A 记录保持一致。如果不一致,Dovecot 的hostname参数会出错,用户端配置邮件客户端时会收到“服务器不匹配”的警告。
4.2 SPF 与 DMARC 记录:TXT 抄不对就是退信
先进入后台的 Configuration → ARC/DKIM keys,选择刚才添加的域名,点击生成 DKIM 密钥对。生成之后页面会显示一条v=DKIM1; k=rsa; p=...的 TXT 记录,把它完整复制出来,主机名填dkim._domainkey,添加到 DNS。这一步做错了,发出的信在对方服务器验签时会直接失败。
SPF 记录相对简单,我在example.com的 TXT 记录里加:
v=spf1 mx a ip4:服务器公网IP -all这个记录表示:只有这个域名的 MX 服务器、A 记录对应的服务器,以及列出来的这个 IP 有权限代表example.com发信。这里的-all是硬失败,对防止别人冒用域名发垃圾信很有效。如果你不确定邮件是否还会从其它服务器发出,可以先用~all(软失败)过渡,但别长期用,否则反垃圾系统对不在列表里的来源会降低信任度。
DMARC 是最后一条防线,告诉对方服务器“如果 SPF 或 DKIM 都没通过,怎么处理这封信”。我的配置是:
v=DMARC1; p=quarantine; rua=mailto:admin@example.comp=quarantine表示把没通过验证的信放进垃圾箱,而不是直接退回。rua是汇总报告的接收地址,用来观察有多少封信没通过验证。验证全部配置可以用一条命令同时查三条记录:
dig TXT dkim._domainkey.example.com dig TXT example.com dig TXT _dmarc.example.com检查输出里三条记录的值是否和后台一致。DNS 同步可能需要几分钟到几小时,但解析生效后再开始正式发信,不然测试邮件会被退回来。
4.3 Rspamd 的默认反垃圾参数:能改和不能改的
Mailcow 内置的 Rspamd 反垃圾模块在开箱状态下已经能拦截大部分垃圾邮件。后台的 Filter → 反垃圾设置里,可以看到不同指标的评分权重,系统会把所有评分加起来,超过阈值就判定为垃圾。这个阈值我一般不动,因为改低了会把正常促销邮件误杀,改高了垃圾邮件会漏进来。
真正值得调的是白名单。如果某个固定合作伙伴的服务器偶尔被误判,可以在 Rspamd 配置里把对方的 IP 或域名加入白名单。常见做法是进入容器编辑 rspamd 的覆盖配置:
docker compose exec rspamd-mailcow sh进入容器后在/etc/rspamd/local.d/下添加一个白名单文件,这个目录是 Rspamd 专门留给管理员做本地覆盖的,不会被 Mailcow 更新覆盖。加完配置需要重启 rspamd 容器才生效。这里要提醒一点:不要直接改/etc/rspamd/rspamd.conf主文件,Mailcow 升级时会把主文件覆盖掉,改完的东西全丢。
另一个高频误杀来源是 Rspamd 对“新域名”的评分偏低信度。刚建好的域名没有发信历史,Rspamd 会把它的邮件标记为低信誉来源,导致部分正常邮件被投进垃圾箱。这不是配置错误,等域名正常收发一段时间后,评分会自然升上来。
5. Mailcow 部署避坑:端口冲突、队列卡死与 Nginx 前置的 4 个坑
把 Mailcow 跑起来只是开始,真正考验人的是部署后的踩坑。这一章的四个案例都是我实际遇到过、或者和群友反复讨论过的典型问题,按现象、原因、解决三步说清楚。
5.1 改了 HTTP_PORT 后页面打不开:只重启 nginx 不够
现象:本机之前已经跑着 Nginx 占用了 80 和 443 端口,安装 Mailcow 之前我在mailcow.conf里把 HTTP_PORT 改成了 8080,HTTPS_PORT 改成 8443,然后启动。浏览器访问https://mail.example.com:8443,Nginx 页面能打开,但点击 Webmail 入口后一直转圈,控制台报 502。
原因:Mailcow 的容器之间不是简单通过宿主机端口通信。Nginx 容器要转发请求给 SOGo 和 Dovecot 容器,这些内部调用走的是 Docker 自定义网络,端口映射在 Compose 层统一管理。只改配置文件不重新创建容器网络,内部调用的端口就还是旧值,Nginx 自然找不到后端。
解决:改完mailcow.conf里任何端口相关参数后,必须执行完整重启:
docker compose down docker compose up -ddocker compose restart只会重启容器进程,不会重新创建网络和重新应用 Compose 里的端口映射。另外,改端口之后要确认新端口没被系统里其它服务占用,用ss -lntp | grep 8443提前查一下。
5.2 投递队列卡死:大量信被 Rspamd 判成垃圾
现象:公司群发了一封产品通知,几分钟后用户反馈没收到。登录服务器执行docker compose exec postfix-mailcow postqueue -p,发现队列里有几百封待发信,全部停在“正在投递”状态。
原因:新域名邮件信誉度低,加上群发邮件带有链接和图片,Rspamd 把大批正常邮件判成了垃圾。Postfix 投递失败后会反复重试,但每次重试间隔越来越长,队列就不动了。
解决:先在后台 Filter → 反垃圾设置里查看 Rspamd 对这几封信的详细评分,确认是误判。然后在 Rspamd 白名单里加上公司自己的发信服务器 IP,再把队列重新投递:
docker compose exec postfix-mailcow postqueue -fpostqueue -f会强制尝试重新投递队列里所有信件。如果确认某些信确实是垃圾邮件并不需要发送,可以先删除再刷新队列:
docker compose exec postfix-mailcow postsuper -d ALL这个命令会清空整个队列,用时务必谨慎。我的习惯是先把误判的信件单独导出查看一下内容,确认没有重要邮件再批量处理。
5.3 磁盘满导致 Dovecot 只读:日志轮转与数据目录分离
现象:某天用户反馈邮箱能登录但收不到新信,查看 Dovecot 日志提示文件系统只读,df -h显示根分区 100% 已满。
原因:Docker 容器产生的日志默认全局保存在系统盘,容器长期运行后日志文件膨胀;同时邮件数据也在持续增加,这两个因素叠加,系统盘被写满。MySQL 检测到磁盘写不进去会自动进入只读保护模式,Dovecot 也随之拒写。
解决:先把容器日志限制加上。在/etc/docker/daemon.json中配置日志轮转:
{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }改完执行systemctl restart docker让配置生效,但注意这只会影响新创建的容器,已有的容器还需要docker compose up -d重建才生效。同时把/var/lib/docker迁移到大容量数据盘,迁移方法是用rsync复制目录后修改 Docker 的>location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass https://127.0.0.1:8443; }
关键是proxy_pass指向 Mailcow 的 HTTPS 端口,同时X-Forwarded-Proto设置成$scheme,让后端知道外部请求其实是 HTTPS。只配了转发没用,不传协议头 SOGo 就会一直认为你是不安全连接,这也是 Webmail 登录循环最常见的翻车点。
6. 日常运维别瞎动:备份恢复与更新顺序的实操技巧
Mailcow 的优势在于统一管理,但反面是它把多个服务耦合在一起,备份和更新都要讲究顺序。最后一章讲点平时不显眼、关键时候救命的运维技巧。
6.1 备份:mailcow.conf 与数据卷必须一起备份
备份 Mailcow 不是备份几个文件的事,而是要把配置、数据库、邮件数据三部分合在一起。我的备份命令是在服务器上把整个目录打包:
tar czf mailcow-backup-$(date +%F).tar.gz mailcow.conf docker-compose.yml data/data/目录下包含所有容器的持久化数据:MySQL 数据库文件在data/conf/mysql,DKIM 私钥在data/conf/dkim,邮件数据在data/vmail下。这三样缺一不可:没有mailcow.conf,恢复后不知道数据库密码;没有 DKIM 私钥,原来签发的邮件签名全部失效;没有邮件数据,用户信件直接丢光。
恢复流程是把备份拷到新服务器对应位置,docker compose up -d启动后执行数据库恢复脚本。恢复时一定要先停掉正在运行的容器再动数据文件,我见过有人直接往运行中的 MySQL 里塞数据文件,结果启动失败只能从头重建。备份文件的大小可以作为健康检查指标,如果某天备份体积突然大幅缩小,先查邮件数据是否被误删。
6.2 更新:先备份,再看变更,再拉取
Mailcow 更新频率不算慢,社区版每次更新前我都会先走一遍固定的检查顺序:
git fetch --all git log --oneline HEAD..origin/master先看从当前版本到最新版之间的提交记录,确认没有涉及数据库结构变更或破坏性调整。如果只看到小修小补,再执行:
docker compose pull docker compose up -ddocker compose pull只拉镜像,up -d会用新镜像重建变化的容器,这个过程中邮件服务会短暂中断几秒到几十秒。我一般把更新安排在凌晨低峰期,同时在更新前完整做一次 6.1 的备份。有一次我没做备份直接拉新版,结果数据库迁移脚本卡住,回滚时发现旧镜像已经被覆盖,最后花了两个小时从备份里恢复。从那以后,不备份不更新成了铁律。
6.3 验证一封外发邮件的完整链路:日志、DNS 与投递状态
更新完之后,发一封测试邮件到外部邮箱,然后回到服务器确认整条链路的状态。先在 Postfix 日志里看投递结果:
docker compose logs postfix-mailcow | grep "status=sent"如果日志里出现status=sent (250 2.0.0 Ok),说明邮件已经成功交给对方服务器。再检查 DKIM 记录是否和后台一致:
dig TXT dkim._domainkey.example.com把返回的p=值和后台 Configuration → ARC/DKIM keys 里显示的 DKIM 公钥对比,确认没有因为后台重签导致 DNS 记录过期。最后用外部邮箱查看原始邮件头,确认收到的信里有dkim=pass和spf=pass标记,这才算一封技术意义上“完全健康”的邮件。
这套验证流程我每次配置新域名或调整反垃圾策略后都会走一遍。邮件服务是那种平时没存在感、出问题时所有用户同时来问的系统;把验证做成固定习惯,就能在用户发现之前先把问题处理掉。希望这篇笔记能帮你少走一些我走过的弯路——特别是备份那一条,拿出来复用的次数越多,越明白它值得。
本文还有配套的精品资源,点击获取