Apache SpamAssassin Docker Compose 部署指南
使用 Docker Compose 一键部署 Apache SpamAssassin 3.4.6 spamd 服务,自建 ubuntu:22.04 基础镜像并注入 --listen 与 --allowed-ips 启动参数,持久化 local.cf 配置与 Bayes 数据卷保证实例可重建数据不丢失,附 spamc -K 健康检查、跨实例客户端调用验证与 unauthorized connection 排障,命令与回显均实测。
Apache SpamAssassin Docker Compose 部署指南
目标:使用 Docker Compose 编排 Apache SpamAssassin 3.4.6 的 spamd 守护进程,通过自建镜像注入监听地址与访问控制参数,实现「一条 docker compose 命令拉起垃圾邮件过滤服务」的轻量部署;端口 783 映射到宿主机,local.cf 配置与 Bayes 数据落盘持久化。全程命令与回显均为实际执行结果。
一、环境信息
- Docker:24.x(本指南实测环境)
- Docker Compose:v5.5.1(
docker compose子命令) - 基础镜像:
ubuntu:22.04(自建镜像spamassassin:3.4.6) - SpamAssassin 版本:3.4.6-1ubuntu0.22.04.1(镜像内 apt 安装)
- 服务名:spamd;实例名:spamassassin-server
- 映射端口:783(spamd)→ 宿主机 783
- 配置挂载:
./conf/local.cf→/etc/mail/spamassassin/local.cf(只读) - 数据挂载:
./data→/var/lib/spamassassin(Bayes 数据库、编译规则)
二、镜像选型说明
| 镜像 | 说明 |
|---|---|
spamassassin:3.4.6(自建,本指南采用) | 基于 ubuntu:22.04,apt 安装 spamassassin + spamc,以 debian-spamd 用户前台运行 spamd,参数集中写在 CMD |
| 社区第三方镜像 | Docker Hub 上有若干打包 spamd 的第三方镜像,版本更新滞后且启动参数不透明;自建镜像仅 5 行 Dockerfile,版本可控,推荐 |
💡 SpamAssassin 官方不发布 Docker 镜像,镜像内核心就是
apt-get install spamassassin spamc;自建可以精确锁定 Ubuntu 与 SpamAssassin 版本组合(本指南实测为 ubuntu:22.04 + 3.4.6-1ubuntu0.22.04.1)。
三、前置条件
- 装有 Docker 与 Docker Compose v2 的服务器,root 权限。
- 能访问 Ubuntu 软件源
archive.ubuntu.com(镜像构建阶段 apt 安装)。 - 放通宿主机 783/TCP 端口(供客户端
spamc调用)。
四、部署步骤
4.1 创建目录结构
mkdir -p spamassassin-compose/conf spamassassin-compose/data
cd spamassassin-compose
4.2 编写 Dockerfile
cat > Dockerfile <<'EOF'
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
spamassassin spamc ca-certificates tzdata \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN usermod -d /var/lib/spamassassin debian-spamd 2>/dev/null || true \
&& mkdir -p /var/lib/spamassassin \
&& chown -R debian-spamd:debian-spamd /var/lib/spamassassin
EXPOSE 783
CMD ["/usr/sbin/spamd", "--listen=0.0.0.0", "--allowed-ips=127.0.0.0/8,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16", "--max-children=5", "--create-prefs", "--helper-home-dir=/var/lib/spamassassin", "--username=debian-spamd", "--groupname=debian-spamd"]
EOF
💡 关键点:
--listen=0.0.0.0允许接收非本机流量;--allowed-ips必须包含127.0.0.0/8(同实例内 spamc 与健康检查都从回环发起)、Docker 网段172.16.0.0/12、宿主机内网192.168.0.0/16;不带-d即前台运行,作为 PID 1 接收信号。
4.3 编写 conf/local.cf
cat > conf/local.cf <<'EOF'
required_score 5.0
rewrite_header Subject *****SPAM*****
report_safe 1
trusted_networks 172.16.0.0/12 10.0.0.0/8 192.168.0.0/16
use_bayes 1
bayes_auto_learn 1
bayes_min_ham_num 200
bayes_min_spam_num 200
bayes_path /var/lib/spamassassin/bayes
EOF
4.4 编写 docker-compose.yml
cat > docker-compose.yml <<'EOF'
services:
spamd:
build: .
image: spamassassin:3.4.6
container_name: spamassassin-server
restart: unless-stopped
ports:
- "783:783"
volumes:
- ./conf/local.cf:/etc/mail/spamassassin/local.cf:ro
- ./data:/var/lib/spamassassin
healthcheck:
test: ["CMD-SHELL", "spamc -K < /dev/null"]
interval: 30s
timeout: 10s
retries: 3
EOF
4.5 构建镜像并启动
docker compose build
docker compose up -d
docker compose ps
真实回显(build 末段):
#8 unpacking to docker.io/library/spamassassin:3.4.6 1.8s done
#8 DONE 5.3s
#9 resolving provenance for metadata file
#9 DONE 0.0s
Image spamassassin:3.4.6 Built
真实回显(up -d 与 ps):
Container spamassassin-server Created
Container spamassassin-server Starting
Container spamassassin-server Started
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
spamassassin-server spamassassin:3.4.6 "/usr/sbin/spamd --l…" spamd 41 seconds ago Up 40 seconds (healthy) 0.0.0.0:783->783/tcp, [::]:783->783/tcp
启动日志:
docker compose logs --no-color | tail -6
真实回显:
spamassassin-server | Oct 12 00:52:37.851 [1] info: spamd: server started on IO::Socket::IP [0.0.0.0]:783 (running version 3.4.6)
spamassassin-server | Oct 12 00:52:37.852 [1] info: spamd: server pid: 1
spamassassin-server | Oct 12 00:52:37.855 [1] info: spamd: server successfully spawned child process, pid 8
spamassassin-server | Oct 12 00:52:37.859 [1] info: spamd: server successfully spawned child process, pid 9
spamassassin-server | Oct 12 00:52:37.860 [1] info: prefork: child states: IS
spamassassin-server | Oct 12 00:52:37.861 [1] info: prefork: child states: II
4.6 GTUBE 判定验证
把宿主机准备好的 GTUBE 测试邮件(正文含标准串 XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X,构造方法见《Apache SpamAssassin 安装指南》4.6 节)拷入实例并验证:
docker cp /tmp/gtube.eml spamassassin-server:/tmp/gtube.eml
docker exec spamassassin-server bash -c 'spamc -c < /tmp/gtube.eml; echo GTUBE_EXIT=$?'
docker exec spamassassin-server bash -c 'spamc < /tmp/gtube.eml | grep -E "^Subject|X-Spam-Status"'
真实回显:
1000.0/5.0
GTUBE_EXIT=1
Subject: *****SPAM***** Test spam mail (GTUBE)
X-Spam-Status: Yes, score=1000.0 required=5.0 tests=GTUBE,NO_RECEIVED,
跨实例客户端验证(在 compose 默认网络内另起一个带 spamc 的实例访问服务名):
docker run --rm --network spamassassin-compose_default \
-v /tmp/gtube.eml:/tmp/gtube.eml:ro spamassassin:3.4.6 \
bash -c 'spamc -d spamassassin-server -p 783 -c < /tmp/gtube.eml; echo CROSS_EXIT=$?'
真实回显:
1000.0/5.0
CROSS_EXIT=1
五、数据持久化说明
| 挂载路径 | 宿主机目录 | 内容 | 说明 |
|---|---|---|---|
/etc/mail/spamassassin/local.cf | ./conf/local.cf | 站点配置 | 只读挂载,改配置后 docker compose restart 生效 |
/var/lib/spamassassin | ./data | Bayes 数据库(bayes_toks/bayes_seen)、编译规则 | 读写挂载,sa-learn 训练结果落盘 |
学习持久化验证:
docker exec spamassassin-server bash -c 'sa-learn --spam /tmp/gtube.eml 2>&1 | tail -1'
ls -la ./data/
真实回显:
Learned tokens from 1 message(s) (1 message(s) examined)
total 32
drwxr-xr-x 2 root root 4096 10月 12 08:54 .
drwxr-xr-x 4 root root 4096 10月 12 08:51 ..
-rw------- 1 root root 12288 10月 12 08:54 bayes_seen
-rw------- 1 root root 12288 10月 12 08:54 bayes_toks
⚠️
./data目录默认由 root 拥有,而 spamd 以debian-spamd运行;sa-learn经docker exec以 root 执行可直接写入。若需要 spamd 进程内自动学习(bayes_auto_learn)写该卷,先在宿主机chown -R 103:104 ./data(debian-spamd 的 UID/GID)。
六、常用环境变量表
| 环境变量 | 示例值 | 说明 |
|---|---|---|
TZ | Asia/Shanghai | 日志时间戳时区,默认 UTC;在 docker-compose.yml 的 environment: 中注入 |
DEBIAN_FRONTEND | noninteractive | 仅构建期使用(Dockerfile 中已设置),避免 apt 交互卡住 |
💡 spamd 本身不读业务环境变量,全部行为由
command参数与挂载的 local.cf 决定;除时区外无需额外注入变量。
七、常用配置参数说明(command)
spamd 启动参数(CMD 数组)逐项说明,均为 spamd --help 实测项:
| 参数 | 本指南取值 | 说明(默认值) |
|---|---|---|
--listen=0.0.0.0 | 0.0.0.0 | 监听地址;默认 --listen localhost 仅回环 |
--allowed-ips=... | 127.0.0.0/8,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16 | 客户端 IP 白名单;不配则仅回环可连 |
--max-children=5 | 5 | 最大并发子进程数,按 CPU/内存调 |
--create-prefs | 开 | 为用户自动创建 user_prefs 文件 |
--helper-home-dir=/var/lib/spamassassin | 数据目录 | 辅助进程 HOME,与数据卷一致 |
--username=debian-spamd / --groupname=debian-spamd | debian-spamd | 降权运行账户(UID 103/GID 104) |
不带 -d | — | 前台运行,作为 PID 1;后台常驻方式才用 -d |
八、配置文件持久化(conf 挂载)
./conf/local.cf 只读挂载到 /etc/mail/spamassassin/local.cf,修改流程:
vi conf/local.cf
docker exec spamassassin-server spamassassin --lint
docker compose restart
docker compose ps
真实回显(lint 干净、重启后健康):
active
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
spamassassin-server spamassassin:3.4.6 "/usr/sbin/spamd --l…" spamd 5 minutes ago Up 20 seconds (healthy) 0.0.0.0:783->783/tcp, [::]:783->783/tcp
💡 也可以用
docker compose exec spamd spamassassin --cf="required_score 8" -t /tmp/gtube.eml做临时参数验证(不影响挂载配置),实测可看到required=8.0生效。
九、等价 docker run 运行方式
不使用 Compose 时,等价的一条 docker run(镜像已由 4.2 节构建):
docker run -d --name spamassassin-standalone --rm -p 1783:783 \
-v /opt/data/quzuo/spamassassin-compose/conf/local.cf:/etc/mail/spamassassin/local.cf:ro \
spamassassin:3.4.6
真实回显:
9d9b4f9da2ab97412a86c97c9765085290750f04647cb1b4c8d286573f0cc970
docker logs spamassassin-standalone | tail -3
docker exec spamassassin-standalone bash -c 'echo -n "" | spamc -K; echo EXIT=$?'
docker rm -f spamassassin-standalone
真实回显:
Oct 12 01:05:51.470 [1] info: spamd: server successfully spawned child process, pid 9
Oct 12 01:05:51.471 [1] info: prefork: child states: IS
Oct 12 01:05:51.473 [1] info: prefork: child states: II
SPAMD/1.5 0
EXIT=0
💡 演示用
-p 1783:783避免与 Compose 实例端口冲突;--rm表示退出即清理,生产建议改为--restart unless-stopped并去掉--rm。
十、常用运维命令
docker compose ps # 实例状态与健康检查结果
docker compose logs -f --no-color # 跟踪 spamd 日志(连接、判定结果都在这里)
docker compose restart # 配置变更后重启
docker compose stop / down # 停止 / 停止并移除实例与网络
docker compose up -d --build # 改 Dockerfile 后重建镜像并滚动替换
docker compose exec spamd sa-learn --dump magic # 查看 Bayes 统计(nspam/nham/ntokens)
docker compose exec spamd sa-update # 实例内更新规则(建议配合镜像重建固化)
判定链路日常验证:
docker exec spamassassin-server bash -c 'spamc -c < /tmp/gtube.eml'
docker exec spamassassin-server bash -c 'spamc -y < /tmp/gtube.eml'
真实回显:
1000.0/5.0
GTUBE,NO_RECEIVED,NO_RELAYS
十一、常见问题(FAQ)
Q1:日志刷 spamd: unauthorized connection from 127.0.0.1,本机 spamc 全部返回 0/0?
--allowed-ips 白名单没包含回环网段,同实例内发起的连接(含健康检查)全部被拒。真实回显:
spamassassin-server | Oct 12 00:53:05.935 [8] warn: spamd: unauthorized connection from 127.0.0.1 [127.0.0.1]:57020 to port 783, fd 5 at /usr/sbin/spamd line 1627.
处理:Dockerfile 的 CMD 中把 127.0.0.0/8 加入 --allowed-ips(见 4.2 节),docker compose up -d --build 重建后恢复;实测恢复后 spamc -c < /tmp/gtube.eml 返回 1000.0/5.0。
Q2:healthcheck 一直 unhealthy?
健康检查不要用 spamc -c < 文件——文件内容会被当作邮件打分,例如 spamc -c < /etc/hostname 实测返回 7.9/5.0、退出码 1,导致永远 unhealthy。正确做法是使用专用的 keepalive 探测:
docker exec spamassassin-server bash -c 'echo -n "" | spamc -K; echo EXIT=$?'
真实回显:
SPAMD/1.5 0
EXIT=0
即 4.4 节 healthcheck 中的 spamc -K < /dev/null。
Q3:宿主机 spamc 连 127.0.0.1:783 连不上?
那是宿主机端口映射后的地址,连接源 IP 是 Docker 网桥网关(如 172.17.0.1),必须在 --allowed-ips 放行对应网段;更推荐跨实例方式调用(4.6 节 CROSS_EXIT=1 实测),把白名单收敛到业务网段。
Q4:Bayes 学习结果重启后丢失?
确认 docker-compose.yml 中 ./data:/var/lib/spamassassin 卷已挂载,且 conf/local.cf 中 bayes_path /var/lib/spamassassin/bayes 指向数据卷路径;未挂载时 Bayes 文件写在可写层,docker compose down && up 后即丢失(本指南 4.4/5 节配置已落盘验证,见第五章 bayes_toks/bayes_seen 回显)。
Q5:镜像内如何更新规则集?
实例内 docker compose exec spamd sa-update 即时生效(reload spamd 需重启实例);长期方案是把 RUN sa-update 写进 Dockerfile 固化进镜像,随 docker compose up -d --build 发布,保证实例可重建。
十二、参考链接
- Apache SpamAssassin 官方站点:https://spamassassin.apache.org/
- spamd 守护进程文档:https://spamassassin.apache.org/doc.html
- GTUBE 标准测试串:https://spamassassin.apache.org/gtube/
- Ubuntu 软件包页:https://launchpad.net/ubuntu/jammy/+source/spamassassin
- Docker Compose 官方文档:https://docs.docker.com/compose/