Courier MTA 1.0.16 Docker Compose 部署指南

Courier MTA 1.0.16 Docker Compose 部署指南:无官方镜像前提下的 ubuntu:22.04+deb 自建镜像形态,entrypoint 修三大坑(me 缺失/bind mount 遮蔽队列目录/netbase 缺 /etc/services)、courierd 就绪等待、四端口验收、587 STARTTLS AUTH 实测与 restart 持久化验证。实测日期 2026-10-25。

Courier MTACourier MTA邮件服务器DockerDocker Compose2026/10/25

Courier MTA 1.0.16 Docker Compose 部署指南

实测日期 2026-10-25。镜像内为 Ubuntu 22.04 + 官方 deb(courier-mta 1.0.16-3build3)。示例域 quzuo-test.cn,示例账号 admin / q1w2e3r4。

一、先说清楚:没有官方镜像

Docker Hub 实查:Courier 官方不发布任何 Docker 镜像,搜到的社区镜像(如 camptocamp/courier-imap)多年未更新、星数寥寥,不可作为生产基础。因此本文采用**「ubuntu:22.04 基础镜像 + 内装官方 deb」的自建镜像 Compose 形态**——版本与 Ubuntu 22.04 安装指南完全一致,升级路径清晰。

二、项目结构

courier-compose/
├── Dockerfile
├── entrypoint.sh
└── compose.yaml

2.1 Dockerfile

FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      courier-mta courier-imap courier-pop courier-authdaemon \
      ca-certificates netbase \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 25 110 143 465 587 993 995
ENTRYPOINT ["/entrypoint.sh"]

netbase 不能省:--no-install-recommends 不带 netbase,容器内没有 /etc/services,couriertcpd 解析端口名 smtp 会报 Invalid port: smtp 导致 25 端口起不来。这是实测踩出来的坑。

2.2 entrypoint.sh

容器没有 systemd,用 init 脚本逐个拉起各守护进程;有几个「空的 bind mount 会遮蔽镜像内目录」的坑必须处理:

#!/bin/bash
set -e

: "${MAIL_DOMAIN:=quzuo-test.cn}"
: "${ADMIN_PASSWORD:=q1w2e3r4}"

echo "mail.${MAIL_DOMAIN}" > /etc/mailname
# /etc/courier/me 由 postinst 依据 /etc/mailname 生成;镜像构建期没有 mailname,需在此补齐
[ -s /etc/courier/me ] || cp /etc/mailname /etc/courier/me

# 开启提交端口(587)与 SMTP AUTH(TLS 会话内)
sed -i 's/^ESMTPDSTART=NO/ESMTPDSTART=YES/' /etc/courier/esmtpd-msa
sed -i 's/^ESMTPAUTH_TLS=""/ESMTPAUTH_TLS="PLAIN LOGIN CRAM-MD5 CRAM-SHA1 CRAM-SHA256"/' /etc/courier/esmtpd

# 补 postmaster 别名(Debian 默认为空,postmaster 邮件会被丢弃)
sed -i 's/^postmaster:$/postmaster: admin/' /etc/courier/aliases/system
makealiases

# 演示用户(幂等)
if ! id admin >/dev/null 2>&1; then
  useradd -m -s /bin/bash admin
  echo "admin:${ADMIN_PASSWORD}" | chpasswd
  su - admin -c "maildirmake /home/admin/Maildir"
fi

# 数据卷初始化与属主修正:空的 bind mount 会遮蔽镜像内已建好的队列目录,
# courierd 找不到 tmp/msgs/msgq 会静默退出,这里按 Debian 布局补齐
for d in tmp msgs msgq; do mkdir -p "/var/lib/courier/$d"; chmod 750 "/var/lib/courier/$d"; done
chown -R courier:courier /var/lib/courier

# 无 systemd 环境下用 init 脚本逐个拉起各守护进程
/etc/init.d/courier-authdaemon start
courier start
# courierd 需要先完成初始化(建 /run/courier 与队列目录),否则紧随其后的 esmtpd 启动会失败
i=0
while ! pgrep -x courierd >/dev/null 2>&1; do
  i=$((i+1)); [ "$i" -gt 10 ] && break; sleep 1
done
sleep 1
/usr/sbin/esmtpd start
/usr/sbin/esmtpd-msa start
/etc/init.d/courier-imap start
/etc/init.d/courier-pop start

echo "Courier stack up: smtp=25 msa=587 imap=143 pop3=110"
# 前台保活并跟随邮件日志
touch /var/log/mail.log
tail -F /var/log/mail.log

三个易踩的坑都在脚本里修了,展开说:

  1. /etc/courier/me 缺失:deb 的 postinst 在安装时依据 /etc/mailname 生成 me(本机规范主机名),而构建镜像时没有 mailname,运行时必须补。
  2. 队列目录被遮蔽:镜像里 postinst 建好的 /var/lib/courier/{tmp,msgs,msgq} 会被空的 bind mount 整个盖住,courierd 找不到队列目录静默退出(无报错、无日志),必须运行时补齐目录并 chown courier:courier。
  3. 启动顺序:courier start(courierd)需要先跑起来建好运行时目录,再启动 esmtpd/msa,否则 esmtpd 启动失败。脚本用 pgrep -x courierd 循环等待(注意必须用 -x 精确匹配,pgrep -f 会把包含 courierd 字样的 shell 命令行本身误判为存活)。

2.3 compose.yaml

name: courier-1.0-stack

services:
  courier:
    build: .
    hostname: mail.quzuo-test.cn
    container_name: courier-1.0-mta
    environment:
      MAIL_DOMAIN: quzuo-test.cn
      ADMIN_PASSWORD: q1w2e3r4
    ports:
      - "8134:25"    # SMTP
      - "8135:587"   # Submission
      - "8136:143"   # IMAP
      - "8137:110"   # POP3
    volumes:
      - ./data/courier:/var/lib/courier   # 队列与过滤器状态
      - ./data/home:/home                 # 用户 Maildir
    restart: unless-stopped

宿主机 8134–8137 是本专栏实验槽位端口,自行替换为 25/587/143/110 或你的规划端口。465/993/995(SSL 端口)容器内已 EXPOSE,需要时加映射即可。

三、启动与验收

docker compose build
docker compose up -d
$ docker compose ps
NAME              STATUS          PORTS
courier-1.0-mta   Up 25 seconds   465/tcp, 993/tcp, 995/tcp, 0.0.0.0:8134->25/tcp, [::]:8134->25/tcp, 0.0.0.0:8137->110/tcp, [::]:8137->110/tcp, 0.0.0.0:8136->143/tcp, [::]:8136->143/tcp, 0.0.0.0:8135->587/tcp, [::]:8135->587/tcp

容器内四端口与 courierd 验收:

courierd-ALIVE
port 25 OPEN
port 110 OPEN
port 143 OPEN
port 587 OPEN

587 STARTTLS + AUTH 实测(AGFkbWluAHExdzJlM3I0 = \0admin\0q1w2e3r4 的 Base64):

$ openssl s_client -connect 127.0.0.1:587 -starttls smtp
EHLO t
250-mail.quzuo-test.cn Ok.
250-AUTH PLAIN LOGIN CRAM-MD5 CRAM-SHA1 CRAM-SHA256
250-XVERP=Courier
250-XEXDATA
250-XSECURITY=NONE,STARTTLS
250-PIPELINING
250-8BITMIME
250-SMTPUTF8
250-SIZE
250 DSN
AUTH PLAIN AGFkbWluAHExdzJlM3I0
235 Ok

收发信实测:

docker exec courier-1.0-mta bash -c 'echo -e "Subject: b2-persist-check\n\npersist body" | sendmail admin'

数秒后邮件落到 ./data/home/admin/Maildir/new/:

1792904217.M364955P276V0000000000000802I00000000005A1C61_1.mail.quzuo-test.cn,S=438
Subject: b2-persist-check

四、持久化验证

邮件队列与 Maildir 都在 bind mount 上,restart 后数据无损:

$ docker compose restart
 Container courier-1.0-mta Started

courierd-ALIVE-AFTER-RESTART
1                        ← Maildir/new 里邮件数量不变
Subject: b2-persist-check
port 25 OPEN
port 143 OPEN
port 587 OPEN
port 110 OPEN

五、运维提示

  • 日志:容器内没有 syslog 守护,courier 的 syslog 输出看不到;排障直接进容器跑命令验证(mailq、courier show all),或自行在镜像里加 rsyslog。
  • 加用户:进容器 useradd + maildirmake,或在 entrypoint 里扩展用户列表循环。
  • 改配置:/etc/courier/* 在容器层,改完 docker compose restart;想持久化配置就把对应文件也 bind mount 出来。
  • 升级:重建镜像即升级(docker compose build --no-cache && docker compose up -d),数据卷不动。
  • 清理:docker compose down,镜像 courier-1.0-stack-courier 用 docker rmi 删除。

六、参考来源