Sendmail 8.18.2 Docker Compose 部署指南
用 Docker Compose 编排自建 Sendmail 8.18.2 镜像与 SMTP 客户端双节点:多阶段构建 Dockerfile 全解、SMTP 横幅与会话验证、跨节点投递端到端闭环(落箱+日志+队列三重验证)、五卷持久化与九则排障实录,全部命令与回显实测照抄。
Sendmail 8.18.2 Docker Compose 部署指南
目标:在装有 Docker Engine 与 Docker Compose 的服务器上,用一份
docker-compose.yml编排出「Sendmail 8.18.2 邮件服务器 + SMTP 客户端机」双节点环境,完成镜像构建、服务启动、SMTP 会话验证与跨节点投递端到端闭环。已在 Docker 29.9.2 / Docker Compose v5.6.1 环境全流程实测通过(2026-10-27)。
一、环境信息
- 操作系统基础:Ubuntu 22.04(构建阶段与运行阶段基础镜像均为
ubuntu:22.04) - Sendmail 版本:8.18.2(官方源码包
sendmail.8.18.2.tar.gz,SHA-256 实测校验1a085faa8ace52cffde2f5e9bc611bdb5f81481caaabf46f0437b719ca089d2f) - 宿主软件版本(实测):Docker 29.9.2、Docker Compose v5.6.1
- 自建镜像:
sendmail:8.18.2(构建产物实测 126MB;Docker Hub 无 Sendmail 官方镜像,本镜像由本文 Dockerfile 从官方源码构建,详见「二、镜像选型说明」) - 编排网络:自定义 bridge 网络
sendmail-8.18.2-net,网段10.89.58.0/24;邮件服务器固定 IP10.89.58.20,客户端机固定 IP10.89.58.21 - 对外端口:宿主机
25→ 邮件服务器25(SMTP 标准端口,宿主侧被占用时按「十一、常见问题」问题 8 调整映射) - Compose 项目名:
sendmail-8.18.2-stack(compose 文件name:显式声明;Compose 规范化后数据卷前缀为sendmail-8182-stack_,点号被移除) - 数据卷:
mqueue(MTA 队列)、clientmqueue(MSP 客户端队列)、mail(用户邮箱)、mailconf(/etc/mail 配置)、maillog(日志)共 5 个命名卷
二、镜像选型说明
Docker Hub 上没有 Sendmail 官方镜像——Sendmail 由 Proofpoint 维护、仅经 ftp.sendmail.org 分发官方源码,从未发布过官方容器镜像。Docker Hub 上的第三方 sendmail 镜像多为个人维护,版本陈旧(普遍停留在 8.15/8.16)且无法核验构建来源。因此本文选择自建镜像:在 Dockerfile 中直接从官方源码包构建,wget 官方地址 + SHA-256 校验保证源码可信,构建过程全部可复现、可审计。
自建镜像采用多阶段构建:
| 阶段 | 基础镜像 | 做什么 | 产出 |
|---|---|---|---|
| build(编译阶段) | ubuntu:22.04 | 装 gcc/make/m4/libdb-dev,下载官方源码包,sh Build -c 编译 + sh Build install 安装,Build sendmail.cf 生成配置 | sendmail、makemap、mailstats、praliases、smrsh 等二进制 + sendmail.cf/submit.cf |
| 运行阶段 | ubuntu:22.04 | 只装运行时依赖(libdb5.3、m4、procmail、rsyslog),COPY --from=build 拷贝二进制与配置 | 精简可运行镜像(实测 126MB) |
编译完成后实测 sendmail -d0.4 输出的编译特性(节选):
Version 8.18.2
Compiled with: DNSMAP IPV6_FULL LOG MATCHGECOS MILTER MIME7TO8 MIME8TO7
NAMED_BIND NETINET NETUNIX NEWDB=5.3 PIPELINING SCANF USERDB
XDEBUG
NEWDB=5.3 表明 Berkeley DB 别名/映射库支持已编入(libdb-dev 提供头文件),MILTER 表明过滤插件协议可用——这两个特性正是镜像内 access.db、aliases.db 与 milter 链路的基础。多阶段构建的取舍:编译阶段约 52MB 的工具链不会进入最终镜像,镜像保持精简且无需在运行阶段再装 gcc。
💡 技术拓展:容器化邮件服务器的镜像来源有三类——官方镜像(如 MySQL、Redis 那类官方发布,Sendmail 不在此列)、第三方个人镜像(版本与供应链不可控)、自建镜像(源码可审计、版本可控)。MTA 这类「配置即生命周期」的软件,自建镜像可以把
sendmail.mc直接固化进镜像构建流程,升级版本只需改 Dockerfile 中的下载 URL 与校验值。本文采用自建镜像,理由:无官方镜像可选,且源码包带官方 SHA-256 可校验。
三、前置条件
- 宿主机已安装 Docker Engine 与 Docker Compose(实测版本回显):
docker --version && docker compose version
实测回显:
Docker version 29.9.2, build a82e528
Docker Compose version v5.6.1
- 宿主机 25 端口空闲(若宿主已装 postfix/exim/sendmail 等本机 MTA 会占用 25,需先停用或改映射)。预检命令:
ss -ltn | grep ':25 ' || echo "port 25 free"
实测回显(预检时端口空闲无输出,走 || 分支):
port 25 free
- 放行防火墙 25 端口(
ufw allow 25/tcp或安全组规则),否则外部主机无法连入。 - 部署目录:本文统一使用
/opt/sendmail-8.18.2/存放 Dockerfile、entrypoint.sh 与 compose 文件。
四、部署步骤
本章从零完成「写构建文件 → 建镜像 → 起服务 → 验证 → 投递闭环」全流程。构建文件共 4 个:Dockerfile(多阶段构建定义)、domain-name.m4(域名宏片段)、entrypoint.sh(服务入口脚本)、docker-compose.yml(编排定义)。
4.1 编写构建文件
先建目录,然后逐个落文件:
mkdir -p /opt/sendmail-8.18.2 && cd /opt/sendmail-8.18.2
文件 1:Dockerfile(多阶段构建,全文如下):
# sendmail 8.18.2 自建镜像:多阶段构建(源码编译 + 精简运行时)
# 阶段一:源码编译
FROM ubuntu:22.04 AS build
ARG http_proxy
ARG https_proxy
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc make m4 wget ca-certificates libdb-dev libc6-dev
WORKDIR /usr/local/src
RUN wget -q https://ftp.sendmail.org/sendmail.8.18.2.tar.gz && \
echo "1a085faa8ace52cffde2f5e9bc611bdb5f81481caaabf46f0437b719ca089d2f sendmail.8.18.2.tar.gz" | sha256sum -c && \
tar -xzf sendmail.8.18.2.tar.gz
WORKDIR /usr/local/src/sendmail-8.18.2
RUN printf "APPENDDEF(\`conf_sendmail_ENVDEF', \`-DNEWDB -DACCEPT_BROKEN_BDB_LOCKING -DSTAT')\nAPPENDDEF(\`conf_sendmail_LIBS', \`-ldb')\n" \
> devtools/Site/site.config.m4 && \
sh Build -c > /tmp/build.log 2>&1 && tail -2 /tmp/build.log && \
groupadd smmsp && useradd -g smmsp -d /var/spool/clientmqueue -s /usr/sbin/nologin smmsp && \
groupadd smmta && useradd -g smmta -d /var/lib/sendmail -s /usr/sbin/nologin smmta && \
mkdir -p /var/spool/mqueue /var/spool/clientmqueue /etc/mail /var/lib/sendmail && \
chown smmsp:smmsp /var/spool/clientmqueue && \
chown root:smmsp /var/spool/mqueue && chmod 750 /var/spool/mqueue && \
sh Build install > /tmp/install.log 2>&1 && tail -2 /tmp/install.log
# 生成 cf(generic-linux.mc + confDOMAIN_NAME)
WORKDIR /usr/local/src/sendmail-8.18.2/cf/cf
COPY domain-name.m4 /tmp/dn.m4
RUN cp generic-linux.mc sendmail.mc && \
sed -i '3r /tmp/dn.m4' sendmail.mc && head -5 sendmail.mc && \
sh Build sendmail.cf > /tmp/cfbuild.log 2>&1 && \
sh Build install-cf >> /tmp/cfbuild.log 2>&1 && \
tail -3 /tmp/cfbuild.log
# 阶段二:运行时镜像
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
libdb5.3 m4 procmail rsyslog bash netbase && rm -rf /var/lib/apt/lists/*
RUN groupadd smmsp && useradd -g smmsp -d /var/spool/clientmqueue -s /usr/sbin/nologin smmsp && \
groupadd smmta && useradd -g smmta -d /var/lib/sendmail -s /usr/sbin/nologin smmta && \
useradd -m -s /bin/bash admin
COPY --from=build /usr/sbin/sendmail /usr/sbin/sendmail
RUN ln -s /usr/sbin/sendmail /usr/bin/mailq && \
ln -s /usr/sbin/sendmail /usr/bin/newaliases && \
ln -s /usr/sbin/sendmail /usr/bin/hoststat && \
ln -s /usr/sbin/sendmail /usr/bin/purgestat
COPY --from=build /usr/sbin/mailstats /usr/sbin/mailstats
COPY --from=build /usr/sbin/praliases /usr/sbin/praliases
COPY --from=build /usr/sbin/makemap /usr/sbin/makemap
COPY --from=build /usr/sbin/smrsh /usr/sbin/smrsh
COPY --from=build /etc/mail/sendmail.cf /etc/mail/sendmail.cf
COPY --from=build /etc/mail/submit.cf /etc/mail/submit.cf
COPY --from=build /usr/local/src/sendmail-8.18.2/cf/cf/sendmail.mc /etc/mail/sendmail.mc
COPY --from=build /usr/local/src/sendmail-8.18.2/cf/cf/submit.mc /etc/mail/submit.mc
COPY --from=build /etc/mail/helpfile /etc/mail/helpfile
COPY --from=build /etc/mail/statistics /etc/mail/statistics
RUN printf 'mail.example.com\nlocalhost\nexample.com\n' > /etc/mail/local-host-names && \
printf 'postmaster: root\nmailer-daemon: postmaster\nabuse: postmaster\n' > /etc/mail/aliases && \
mkdir -p /var/spool/mqueue /var/spool/clientmqueue /var/mail /var/lib/sendmail && \
chown root:smmsp /var/spool/mqueue && chmod 750 /var/spool/mqueue && \
chown smmsp:smmsp /var/spool/clientmqueue && \
/usr/sbin/sendmail -bi && echo ALIASES-OK
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 25
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/usr/sbin/sendmail", "-bD", "-q30m"]
逐段说明:
ARG http_proxy / https_proxy:构建期代理参数,默认为空即直连官方源ftp.sendmail.org;网络受限环境构建时用--build-arg http_proxy=... --build-arg https_proxy=...注入代理;- 源码下载行:
wget官方包 +sha256sum -c校验,校验值与 A1 篇实查的官方值一致,校验失败构建即中断; site.config.m4:APPENDDEF把-DNEWDB(Berkeley DB 支持)、-DACCEPT_BROKEN_BDB_LOCKING(接受 Berkeley DB 5.x 无 flock 的官方编译出口,详见《Ubuntu 22.04 安装 Sendmail 8.15.2 指南》排障实录)、-DSTAT(mailstats 统计支持)追加进编译宏,-ldb链接 libdb;sh Build -c > /tmp/build.log 2>&1 && tail -2 /tmp/build.log:编译全量输出(数千行)先重定向到文件再tail取回显。这是刻意设计——编译输出直连构建管道时,一旦读端提前关闭会向编译进程发送 SIGPIPE 导致构建层中断(完整排障见「十一、常见问题」问题 3);- 用户与目录准备:创建
smmsp(MSP 队列属主)、smmta(MTA 属主)两组用户,队列目录/var/spool/mqueue归root:smmsp且750、/var/spool/clientmqueue归smmsp:smmsp——这是 sendmail 8.12+ 安全分权的标准属主关系; - cf 生成段:以上游
generic-linux.mc为底,第 3 行插入define(\confDOMAIN_NAME', `mail.example.com')dnl(即文件 2),sh Build sendmail.cf` 经 m4 生成最终配置并安装; - 运行阶段:
COPY --from=build只拷二进制与配置;建立mailq/newaliases/hoststat/purgestat到 sendmail 的符号链接(Sendmail 传统上这四个命令就是 sendmail 同一二进制的别名);创建演示收件用户admin;/usr/sbin/sendmail -bi初始化别名库并输出ALIASES-OK作为构建期自检; CMD ["-bD", "-q30m"]:-bD前台运行 MTA 守护(PID 1 可被编排系统直接管理),-q30m每 30 分钟跑一次队列。
文件 2:domain-name.m4(插入 sendmail.mc 的域名宏,一行):
define(`confDOMAIN_NAME', `mail.example.com')dnl
confDOMAIN_NAME 定义 $j(规范域名),决定 SMTP 横幅与 Message-Id 中的主机标识。不定义时 Sendmail 会取内核主机名,在容器环境常得到非规范短名。
文件 3:entrypoint.sh(服务入口,全文如下):
#!/bin/bash
# Sendmail 服务入口:先启动 rsyslog(sendmail 日志经 /dev/log 写 /var/log/mail.log),
# 再后台拉起 MSP 客户端队列跑动(本地 sendmail 命令提交的信件从 clientmqueue 送出),最后前台启动 MTA
/usr/sbin/rsyslogd
/usr/sbin/sendmail -Ac -q20m &
echo "entrypoint: rsyslogd + MSP queue runner started, starting sendmail: $@"
exec "$@"
说明:-Ac 指定使用 submit.cf 体系(MSP 角色),-q20m 每 20 分钟跑一次客户端队列;exec "$@" 让 CMD 的 MTA 进程接管 PID 1,信号可直接送达。rsyslogd 必须先起——Sendmail 默认经 syslog 记日志,没有它 /var/log/mail.log 不会产生,投递审计无从谈起。
文件 4:docker-compose.yml(编排定义,全文如下):
name: sendmail-8.18.2-stack
services:
mta:
build:
context: .
dockerfile: Dockerfile
image: sendmail:8.18.2
container_name: sendmail-8.18.2-mta
hostname: mail.example.com
restart: unless-stopped
ports:
- "25:25"
volumes:
- mqueue:/var/spool/mqueue
- clientmqueue:/var/spool/clientmqueue
- mail:/var/mail
- mailconf:/etc/mail
- maillog:/var/log
healthcheck:
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/25 && head -c 4 <&3 | grep -q '^220'"]
interval: 30s
timeout: 5s
retries: 3
networks:
sendmail-net:
ipv4_address: 10.89.58.20
client:
image: ubuntu:22.04
container_name: sendmail-8.18.2-client
hostname: client
command: ["sleep", "infinity"]
networks:
sendmail-net:
ipv4_address: 10.89.58.21
volumes:
mqueue:
clientmqueue:
mail:
mailconf:
maillog:
networks:
sendmail-net:
name: sendmail-8.18.2-net
driver: bridge
ipam:
config:
- subnet: 10.89.58.0/24
gateway: 10.89.58.1
逐项说明:
name: sendmail-8.18.2-stack:显式 Compose 项目名,避免与其他项目同名互相干扰;mta服务:hostname: mail.example.com让容器内主机名与confDOMAIN_NAME一致;ports把宿主 25 映射到邮件服务器 25;5 个命名卷见「五、数据持久化说明」;healthcheck:用 bash 内建/dev/tcp直连本机 25 端口读前 4 字节,匹配220(SMTP 就绪横幅首码)即健康——比pgrep sendmail更真实,验证的是「SMTP 服务可用」而非「进程存在」;client服务:一个ubuntu:22.04客户端机(sleep infinity保活),用于模拟「内网另一台主机经 SMTP 投信」的场景,配合固定 IP10.89.58.21;networks:固定网段10.89.58.0/24与双节点固定 IP,保证 DNS 名(mail.example.com)与 access 控制预期稳定。
4.2 构建镜像
cd /opt/sendmail-8.18.2 && docker compose build
实测回显(输出节选,实测首尾;完整输出约百余行):
Image sendmail:8.18.2 Building
#3 [internal] load metadata for docker.io/library/ubuntu:22.04
#3 DONE 0.1s
...
#11 [build 4/9] RUN wget -q https://ftp.sendmail.org/sendmail.8.18.2.tar.gz && echo "1a085faa8ace52cffde2f5e9bc611bdb5f81481caaabf46f0437b719ca089d2f sendmail.8.18.2.tar.gz" | sha256sum -c && tar -xzf sendmail.8.18.2.tar.gz
#11 2.890 sendmail.8.18.2.tar.gz: OK
#11 DONE 3.0s
...
#27 [stage-1 14/16] RUN printf 'mail.example.com\nlocalhost\nexample.com\n' > /etc/mail/local-host-names && printf 'postmaster: root\nmailer-daemon: postmaster\nabuse: postmaster\n' > /etc/mail/aliases && mkdir -p /var/spool/mqueue /var/spool/clientmqueue /var/mail /var/lib/sendmail && chown root:smmsp /var/spool/mqueue && chmod 750 /var/spool/mqueue && chown smmsp:smmsp /var/spool/clientmqueue && /usr/sbin/sendmail -bi && echo ALIASES-OK
#27 0.250 /etc/mail/aliases: 3 aliases, longest 10 bytes, 52 bytes total
#27 0.252 ALIASES-OK
#27 DONE 0.3s
...
#30 exporting layers 0.3s done
#30 naming to docker.io/library/sendmail:8.18.2 done
#30 DONE 0.4s
Image sendmail:8.18.2 Built
关键观察点:sendmail.8.18.2.tar.gz: OK 是 SHA-256 校验通过;/etc/mail/aliases: 3 aliases, ... + ALIASES-OK 是别名库初始化自检通过;末行 Image sendmail:8.18.2 Built 表示构建成功。构建总耗时约 1–3 分钟(缓存全命中时秒级完成)。验证镜像已生成:
docker images sendmail --format '{{.Repository}}:{{.Tag}} {{.Size}}'
实测回显:
sendmail:8.18.2 126MB
4.3 启动服务并确认健康
cd /opt/sendmail-8.18.2 && docker compose up -d
实测回显:
Container sendmail-8.18.2-client Running
Container sendmail-8.18.2-mta Recreate
Container sendmail-8.18.2-mta Recreated
Container sendmail-8.18.2-mta Starting
Container sendmail-8.18.2-mta Started
首次执行时 client 也会从 Created 起步;Recreate/Recreated 是 compose 检测到镜像/配置变化后重建实例的标准输出。约 30 秒后确认健康状态:
docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
sendmail-8.18.2-client ubuntu:22.04 "sleep infinity" client 37 minutes ago Up 37 minutes
sendmail-8.18.2-mta sendmail:8.18.2 "/entrypoint.sh /usr…" mta Less than a second ago Up Less than a second (health: starting) 0.0.0.0:25->25/tcp, [::]:25->25/tcp
health: starting 表示首轮健康检查未到间隔,30 秒内转为 healthy。宿主侧确认 25 端口监听与服务内部进程:
ss -ltn | grep ':25 '
实测回显:
LISTEN 0 4096 0.0.0.0:25 0.0.0.0:*
LISTEN 0 4096 [::]:25 [::]:*
docker exec sendmail-8.18.2-mta ps aux
实测回显:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 6204 4032 ? Ss 03:40 0:00 sendmail: accepting connections
syslog 7 0.0 0.0 152476 3720 ? Ssl 03:40 0:00 /usr/sbin/rsyslogd
smmsp 12 0.0 0.0 6048 2592 ? Ss 03:40 0:00 sendmail: Queue runner@00:20:00 for /var/spool/clientmqueue
root 514 0.0 0.0 7080 3088 ? Rs 04:08 0:00 ps aux
三个常驻进程正是架构预期:PID 1 的 MTA 守护(sendmail: accepting connections)、rsyslog 日志、以 smmsp 用户运行的 MSP 队列跑动(Queue runner@00:20:00 for /var/spool/clientmqueue,对应 entrypoint 的 -Ac -q20m)。
4.4 SMTP 验证与双节点连通
先验证 SMTP 横幅(宿主机任意目录,bash 内建 /dev/tcp 直连宿主 25 端口):
exec 3<>/dev/tcp/127.0.0.1/25 && head -c 110 <&3; exec 3<&-
实测回显:
220 mail.example.com ESMTP Sendmail 8.18.2/8.18.2; Tue, 27 Oct 2026 03:36:00 GMT
横幅中的 mail.example.com 即 confDOMAIN_NAME 所定义的 $j,8.18.2/8.18.2 为版本号——这是「SMTP 服务在宿主 25 端口可用」的直接证据。再验证客户端机到邮件服务器的内网 DNS 解析(内网 DNS 按节点名解析):
docker exec sendmail-8.18.2-client getent hosts mail.example.com
实测回显:
10.89.58.20 mail.example.com
客户端机可按名解析邮件服务器,这是跨节点投递的前提。
4.5 投递闭环端到端实测
进入客户端机发起一次真实 SMTP 会话:发一封合法邮件(root@example.com → admin@mail.example.com),同时夹带一封对外中继请求验证 access 拦截。会话实录如下(S: 行为服务器回显逐行照抄,C: 行为客户端输入):
C: (客户端建立到 mail.example.com:25 的连接)
S: 220 mail.example.com ESMTP Sendmail 8.18.2/8.18.2; Tue, 27 Oct 2026 03:41:14 GMT
S: 250-mail.example.com Hello sendmail-8.18.2-client.sendmail-8.18.2-net [10.89.58.21], pleased to meet you
S: 250-ENHANCEDSTATUSCODES
S: 250-PIPELINING
S: 250-EXPN
S: 250-VERB
S: 250-8BITMIME
S: 250-SIZE
S: 250-DSN
S: 250-ETRN
S: 250-DELIVERBY
S: 250 HELP
C: MAIL FROM:<root@example.com>
S: 250 2.1.0 <root@example.com>... Sender ok
C: RCPT TO:<admin@mail.example.com>
S: 250 2.1.5 <admin@mail.example.com>... Recipient ok
C: RCPT TO:<nobody@elsewhere.invalid>
S: 550 5.7.1 <nobody@elsewhere.invalid>... Relaying denied
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: root@example.com
C: To: admin@mail.example.com
C: Subject: compose e2e
C:
C: hello from compose client
C: .
S: 250 2.0.0 69R3fEMY000014 Message accepted for delivery
C: QUIT
S: 221 2.0.0 mail.example.com closing connection
会话三要素全部符合预期:合法收件 250 Recipient ok(admin 用户已建且 mail.example.com 在 local-host-names 中)、对外中继 550 Relaying denied(默认仅放行本机,防开放中继)、正文受理 250 Message accepted for delivery(队列号 69R3fEMY000014)。
投递结果做三重验证。验证一:收件人邮箱落箱(查看邮件服务器上 admin 的邮箱尾部):
docker exec sendmail-8.18.2-mta tail -8 /var/mail/admin
实测回显:
Date: Tue, 27 Oct 2026 03:41:14 GMT
Message-Id: <202610270341.69R3fEMY000014@mail.example.com>
From: root@example.com
To: admin@mail.example.com
Subject: compose e2e
hello from compose client
验证二:投递日志(队列号精确检索):
docker exec sendmail-8.18.2-mta grep 69R3fEMY000014 /var/log/mail.log
实测回显:
Oct 27 03:41:17 mail sendmail[14]: 69R3fEMY000014: ruleset=check_rcpt, arg1=<nobody@elsewhere.invalid>, relay=sendmail-8.18.2-client.sendmail-8.18.2-net [10.89.58.21], reject=550 5.7.1 <nobody@elsewhere.invalid>... Relaying denied
Oct 27 03:41:17 mail sendmail[14]: 69R3fEMY000014: from=<root@example.com>, size=98, class=0, nrcpts=1, msgid=<202610270341.69R3fEMY000014@mail.example.com>, proto=ESMTP, daemon=MTA, relay=sendmail-8.18.2-client.sendmail-8.18.2-net [10.89.58.21]
Oct 27 03:41:18 mail sendmail[15]: 69R3fEMY000014: to=<admin@mail.example.com>, ctladdr=<root@example.com> (0/0), delay=00:00:01, xdelay=00:00:01, mailer=local, pri=30442, dsn=2.0.0, stat=Sent
末行 mailer=local ... stat=Sent 表示经 local 投递代理落箱成功;首行则是中继拒绝的审计记录。验证三:双队列空闲确认(信已投出、无滞留):
docker exec sendmail-8.18.2-mta mailq && docker exec sendmail-8.18.2-mta mailq -Ac
实测回显:
/var/spool/mqueue is empty
Total requests: 0
/var/spool/clientmqueue is empty
Total requests: 0
至此「构建 → 启动 → SMTP 验证 → 跨节点投递落箱」全链路闭环完成。
五、数据持久化说明
邮件服务器的三类状态——待投队列、用户邮箱、配置——全部落在命名卷上,实例重建不丢数据:
| 数据卷 | 挂载点 | 内容 | 说明 |
|---|---|---|---|
mqueue | /var/spool/mqueue | MTA 队列(收到的待投邮件) | 最核心数据,断网缓冲所在 |
clientmqueue | /var/spool/clientmqueue | MSP 客户端队列(本机命令提交的邮件) | -Ac 体系队列 |
mail | /var/mail | 用户邮箱(如 /var/mail/admin) | 落箱即此卷 |
mailconf | /etc/mail | sendmail.cf/submit.cf 及映射库 | 配置持久化,见「八、配置文件挂载」 |
maillog | /var/log | rsyslog 产出的 mail.log 等 | 日志留存,审计与排障依据 |
实际落盘的卷名(Compose 自动加项目前缀):
docker volume ls --format '{{.Name}}' | grep sendmail
实测回显:
sendmail-8182-stack_clientmqueue
sendmail-8182-stack_mail
sendmail-8182-stack_mailconf
sendmail-8182-stack_maillog
sendmail-8182-stack_mqueue
清理语义要分清:docker compose down 删除实例与网络但保留数据卷;docker compose down -v 连数据卷一起删除——对邮件系统意味着队列与邮箱全部清空,生产环境严禁随意带 -v。
六、环境变量表
本镜像运行时不依赖任何环境变量:Sendmail 全部行为由 /etc/mail/ 下的配置文件驱动,不读环境变量。镜像暴露的变量只有构建期的代理参数:
| 变量 | 阶段 | 作用 | 默认值 | 示例 |
|---|---|---|---|---|
http_proxy | build | 构建期 apt/wget 走代理(网络受限时) | 空(直连) | --build-arg http_proxy=http://192.168.1.62:7897 |
https_proxy | build | 同上(https 下载走代理) | 空(直连) | --build-arg https_proxy=http://192.168.1.62:7897 |
行为类参数不靠环境变量而靠命令行与配置文件调整:
| 调整点 | 位置 | 默认值 | 改法示例 |
|---|---|---|---|
| MTA 队列跑动间隔 | compose CMD(-q30m) | 30 分钟 | 改为 -q15m 后 docker compose up -d 重建 |
| MSP 队列跑动间隔 | entrypoint.sh(-Ac -q20m) | 20 分钟 | 改脚本后重新 build |
| 队列跑动并发度 | CMD 可加 -qR/-qp 等 | 单跑动 | 见 sendmail(8) 队列参数 |
七、配置参数说明
compose 编排层的可调参数与语义:
| 参数 | 当前值 | 语义 | 调整建议 |
|---|---|---|---|
restart: unless-stopped | 自动重启 | 实例退出(非人工停止)时自动拉起 | 生产建议保留 |
healthcheck.test | /dev/tcp/127.0.0.1/25 读 220 | 以 SMTP 横幅就绪为健康标准 | 不要改成进程存活检查,语义弱 |
healthcheck.interval/retries | 30s / 3 | 每 30 秒探测、连续 3 次失败判不健康 | 排障期可临时拉大 interval |
ports: "25:25" | 宿主 25 → 服务 25 | 宿主 25 被占时改 "8125:25" 并同步防火墙 | 见「十一、常见问题」问题 8 |
固定 IP 10.89.58.20/21 | 编排网络内固定 | 保证 DNS 名与来源 IP 稳定 | 换网段时同步改 subnet 与 IP |
hostname: mail.example.com | 与 confDOMAIN_NAME 一致 | 主机名与邮件域对齐 | 换域名时连 Dockerfile 里 confDOMAIN_NAME 一起改 |
镜像内配置层的核心参数只有一处固化在构建期——sendmail.mc 中的 define(\confDOMAIN_NAME', `mail.example.com')dnl(决定 SMTP 横幅、Message-Id 与规则集 $j);其余配置(监听地址、local-host-names、aliases、access 等)全部可通过 mailconf` 卷挂载覆盖,改动流程见下一章。
八、配置文件挂载
mailconf 卷挂在邮件服务器的 /etc/mail 上,构建期写入的初始配置在首次启动后即持久化在卷中。实测镜像内初始配置清单:
docker exec sendmail-8.18.2-mta ls /etc/mail
实测回显:
aliases
aliases.db
helpfile
local-host-names
sendmail.cf
sendmail.mc
statistics
submit.cf
submit.mc
各文件用途:sendmail.mc(m4 宏源,改配置的入口)、sendmail.cf(由 mc 生成的最终生效配置)、submit.mc/submit.cf(MSP 提交侧配置)、local-host-names(本机收信域名清单)、aliases/aliases.db(别名表及编译产物)、helpfile(SMTP HELP 文案)、statistics(mailstats 统计文件)。
两个初始文件的实际内容:
docker exec sendmail-8.18.2-mta cat /etc/mail/local-host-names
实测回显:
mail.example.com
localhost
example.com
docker exec sendmail-8.18.2-mta cat /etc/mail/aliases
实测回显:
postmaster: root
mailer-daemon: postmaster
abuse: postmaster
local-host-names 三行意味着 admin@mail.example.com、admin@localhost、admin@example.com 三种收件地址都按本机收信——这正是 4.5 节会话中 admin@mail.example.com 被接受为本地收件人的配置依据。
修改配置的标准流程:在实例内改配置源 → 用 m4 重生成 .cf → 重启实例生效(基础镜像未装 vi/vim,文本修改用 sed/追加方式落地)。以追加一条消息大小上限宏为例:
# 1) 追加配置宏(m4 引号是「反引号/单引号」成对;sh 双引号内反引号须写成 \` 防止命令替换)
docker exec sendmail-8.18.2-mta sh -c 'echo "define(\`confMAX_MESSAGE_SIZE', \`10485760')dnl" >> /etc/mail/sendmail.mc'
# 2) 重新生成最终配置(mc → m4 → cf)
docker exec sendmail-8.18.2-mta sh -c 'cd /etc/mail && m4 sendmail.mc > sendmail.cf'
# 3) 重启服务实例使配置生效
cd /opt/sendmail-8.18.2 && docker compose restart mta
改完用 SMTP 横幅复查(回显中版本与域名应保持一致):
exec 3<>/dev/tcp/127.0.0.1/25 && head -c 110 <&3; exec 3<&-
⚠️ 注意:
aliases与 access 类映射库修改后需要各自的重编译步骤(newaliases/makemap),只改文本源不会生效——这是 Sendmail 最常见的「改了没反应」根因,命令细节见《Sendmail 8.18.2 常用 CLI 命令与工具详解》。
九、等价 docker run
不用 Compose 时,可用原生 docker run 达成等价部署。先建网络与数据卷(Compose 中这两步由 networks:/volumes: 声明自动完成):
docker network create --driver bridge --subnet 10.89.58.0/24 --gateway 10.89.58.1 sendmail-8.18.2-net
docker volume create sendmail-8182-stack_mqueue
docker volume create sendmail-8182-stack_clientmqueue
docker volume create sendmail-8182-stack_mail
docker volume create sendmail-8182-stack_mailconf
docker volume create sendmail-8182-stack_maillog
邮件服务器(等价于 mta 服务,需先 docker build -t sendmail:8.18.2 /opt/sendmail-8.18.2/):
docker run -d \
--name sendmail-8.18.2-mta \
--hostname mail.example.com \
--restart unless-stopped \
-p 25:25 \
-v sendmail-8182-stack_mqueue:/var/spool/mqueue \
-v sendmail-8182-stack_clientmqueue:/var/spool/clientmqueue \
-v sendmail-8182-stack_mail:/var/mail \
-v sendmail-8182-stack_mailconf:/etc/mail \
-v sendmail-8182-stack_maillog:/var/log \
--network sendmail-8.18.2-net --ip 10.89.58.20 \
sendmail:8.18.2
客户端机(等价于 client 服务):
docker run -d \
--name sendmail-8.18.2-client \
--hostname client \
--network sendmail-8.18.2-net --ip 10.89.58.21 \
ubuntu:22.04 sleep infinity
差异说明:等价 docker run 缺少 healthcheck 与项目化元数据(编排标签),需自行 docker update --health-cmd ... 补健康检查;多实例/多环境管理场景仍建议以 Compose 为准。
十、常用运维命令
日常运维以 docker compose 子命令为主线(均在 /opt/sendmail-8.18.2/ 目录内执行):
| 命令 | 用途 |
|---|---|
docker compose ps | 查看双节点状态与健康值 |
docker compose logs -f mta | 跟随邮件服务器实例日志 |
docker compose restart mta | 改配置后重启邮件服务器 |
docker compose stop / up -d | 停服 / 起服(保留数据卷) |
docker compose down | 拆除实例与网络(保留数据卷) |
docker compose down -v | 拆除并清空数据卷(队列+邮箱全丢,慎用) |
docker exec sendmail-8.18.2-mta mailq | 查看 MTA 队列 |
docker exec sendmail-8.18.2-mta mailstats -f /etc/mail/statistics | 查看收发统计 |
状态巡检实测回显(与 4.5 节闭环后状态一致):
docker exec sendmail-8.18.2-mta sendmail -d0.4 -bt < /dev/null
实测回显(输出节选,实测首尾):
Version 8.18.2
Compiled with: DNSMAP IPV6_FULL LOG MATCHGECOS MILTER MIME7TO8 MIME8TO7
NAMED_BIND NETINET NETUNIX NEWDB=5.3 PIPELINING SCANF USERDB
XDEBUG
Canonical name: mail.example.com
UUCP nodename: mail.example.com
a.k.a.: mail
a.k.a.: [10.89.58.20]
a.k.a.: [127.0.0.1]
============ SYSTEM IDENTITY (after readcf) ============
(short domain name) $w = mail
(canonical domain name) $j = mail.example.com
(subdomain name) $m = example.com
(node name) $k = mail.example.com
========================================================
ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
>
这份回显同时确认了版本(8.18.2)、编译特性(NEWDB/MILTER)与规范域名三要素,是排障的第一入口。收发统计(统计文件按 -f 显式指定,原因见「十一、常见问题」问题 7):
docker exec sendmail-8.18.2-mta mailstats -f /etc/mail/statistics
实测回显(新栈尚无收发累计,各列为 0):
Statistics from Tue Oct 27 04:08:52 2026
M msgsfr bytes_from msgsto bytes_to msgsrej msgsdis msgsqur Mailer
=====================================================================
T 0 0K 0 0K 0 0 0
C 0 0 0
十一、常见问题(FAQ)
Q1:构建失败,报 groupadd: GID '25' already exists,exit code 4。
现象实录(构建日志节选):
#8 [stage-1 3/14] RUN groupadd -g 25 smmsp && useradd -g smmsp -u 25 -d /var/spool/clientmqueue -s /usr/sbin/nologin smmsp && ...
#8 0.158 groupadd: GID '25' already exists
#8 ERROR: process "/bin/sh -c groupadd -g 25 smmsp && ..." did not complete successfully: exit code: 4
原因:早期 Dockerfile 为对齐传统 Unix 的 UID/GID 硬编码了 -g 25/-u 25,但基础镜像环境里 GID 25 已被占用,groupadd 直接失败。解决:去掉固定 UID/GID,让系统自动分配(本文 Dockerfile 即采用自动分配),Sendmail 只要求 smmsp/smmta 用户存在且属主关系正确,不要求特定数值 ID。
Q2:构建失败,报 cannot create devtools/Site/site.config.m4: Directory nonexistent,exit code 2。
现象实录(构建日志节选):
#12 0.163 /bin/sh: 1: cannot create devtools/Site/site.config.m4: Directory nonexistent
#12 ERROR: ... exit code: 2
原因:源码树解压后 devtools/Site/ 目录默认不存在,重定向写入该路径必然失败。解决:写 site.config.m4 前先 mkdir -p devtools/Site(或像本文 Dockerfile 用 printf ... > devtools/Site/site.config.m4 前保证路径存在;上游 Build 脚本在部分版本会自动创建,但不要依赖该隐式行为)。
Q3:构建偶发中断——长输出经管道传输触发 SIGPIPE。
现象:sh Build 全量编译输出数千行,若把输出经管道直连(如 sh Build | head、sh Build | tee 后读端提前关闭),管道读端关闭时编译进程会收到 SIGPIPE 而提前退出,构建层随之失败,且报错点与真实编译错误无关,极具迷惑性。规避:长输出一律先全量重定向到文件,再从文件取尾部回显——本文 Dockerfile 的 sh Build -c > /tmp/build.log 2>&1 && tail -2 /tmp/build.log 就是这一模式;人工排障时同理,把构建日志重定向到文件后 tail/grep,不要直接管道截断。实测该模式连续多次构建均稳定通过。
Q4:SMTP 会话报 451 4.1.8 Domain of sender address root@client.example.net does not resolve。
现象实录(早期会话,S: 行照抄):
S: 451 4.1.8 Domain of sender address root@client.example.net does not resolve
原因:信封发件人域(client.example.net)无法被邮件服务器解析,Sendmail 拒收。解决:客户端发信时使用可解析的发件域(本文用 example.com,它已写入 local-host-names 与 DNS 可达范围)。
Q5:SMTP 会话报 550 5.1.1 <admin@mail.example.com>... User unknown。
现象实录(早期会话):
S: 550 5.1.1 <admin@mail.example.com>... User unknown
原因:收件本地用户不存在(早期镜像未创建 admin)。解决:在镜像/实例中创建收件用户(本文运行阶段 useradd -m -s /bin/bash admin),或把收件地址经 aliases 别名映射到已存在用户。
Q6:SMTP 会话报 550 5.7.1 <nobody@elsewhere.invalid>... Relaying denied。
这不是故障而是预期防线:邮件服务器默认拒绝为任意外部域中继,仅 local-host-names 中的本机域收信。确需为某网段开放中继时,在 access 源文件加 Connect:<网段> RELAY 并用 makemap 重编译 access.db(完整命令与映射语法见《Sendmail 8.18.2 常用 CLI 命令与工具详解》)。开放前务必确认网段可信,防止沦为垃圾邮件跳板。
Q7:mailstats 报 mailstats: no statistics file located。
现象实录:
===MAILSTATS===
mailstats: no statistics file located
原因:mailstats 默认在 /var/log/statistics 找统计文件,而本镜像构建时上游默认把 statistics 安装在 /etc/mail/statistics。解决:用 -f 显式指定——mailstats -f /etc/mail/statistics(实测回显见「十、常用运维命令」)。
Q8:宿主 25 端口被本机 MTA 占用,docker compose up 报端口绑定失败。
排查与解决:ss -ltn | grep ':25 ' 找到占用者;若是宿主自身 postfix/exim/sendmail,停用并禁用其自启(systemctl stop postfix && systemctl disable postfix 类操作按发行版服务名处理),或把 compose 的 ports 改为 "8125:25"——注意此时外部客户端要连宿主 8125,access/来源 IP 审计也会看到映射后的来源变化。
Q9:docker compose down 之后数据还在吗?
在。down 只删实例与编排网络,5 个命名卷保留;仅 down -v 会连卷一起删(队列、邮箱、配置、日志全清)。日常停机用 stop/down,升级镜像后 up -d 重建实例即可带着原数据回来。
十二、参考链接
- 本系列认知篇:《Sendmail 8.18.2 介绍与选型指南》(版本谱系、许可证与 MTA 选型对比)
- 本系列安装篇:《Ubuntu 22.04 安装 Sendmail 8.15.2 指南》(apt 与源码编译双方案、ACCEPT_BROKEN_BDB_LOCKING 排障实录)
- 本系列命令篇:《Sendmail 8.18.2 常用 CLI 命令与工具详解》(mailq/makemap/newaliases/mailstats 等命令详解)
- Sendmail 官方发布目录(源码包与 RELEASE_NOTES,查询日期 2026-10-27):
https://ftp.sendmail.org/ - Docker Compose 官方文档(compose 文件规范):
https://docs.docker.com/compose/ - Sendmail cf/README(m4 配置体系权威说明):
https://fossies.org/linux/sendmail/cf/README