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 SpamAssassinApache SpamAssassin2026/10/12

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./dataBayes 数据库(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)。

六、常用环境变量表

环境变量示例值说明
TZAsia/Shanghai日志时间戳时区,默认 UTC;在 docker-compose.yml 的 environment: 中注入
DEBIAN_FRONTENDnoninteractive仅构建期使用(Dockerfile 中已设置),避免 apt 交互卡住

💡 spamd 本身不读业务环境变量,全部行为由 command 参数与挂载的 local.cf 决定;除时区外无需额外注入变量。

七、常用配置参数说明(command)

spamd 启动参数(CMD 数组)逐项说明,均为 spamd --help 实测项:

参数本指南取值说明(默认值)
--listen=0.0.0.00.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=55最大并发子进程数,按 CPU/内存调
--create-prefs开为用户自动创建 user_prefs 文件
--helper-home-dir=/var/lib/spamassassin数据目录辅助进程 HOME,与数据卷一致
--username=debian-spamd / --groupname=debian-spamddebian-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 发布,保证实例可重建。

十二、参考链接