EmailRelay 2.6.1 Docker Compose 部署指南

EmailRelay 2.6.1 Docker Compose 部署指南:官方无镜像前提下的两阶段 Dockerfile 自建镜像(201MB)、relay+sink 双服务编排、as-proxy 宏隐含 --close-stderr 导致日志不可见的显式展开解法、8131/8132 端口端到端 swaks 与管理口实测。实测日期 2026-10-25。

EmailRelayEmailRelaySMTPDockerDocker Compose2026/10/25

EmailRelay 2.6.1 Docker Compose 部署指南

目标:用两阶段 Dockerfile 自建 EmailRelay 2.6.1 镜像,并以 Compose 编排「relay(存储转发)+ sink(模拟上游)」双服务完成端到端验证。已在 Docker 环境实测通过,实测日期 2026-10-25。

一、环境信息

  • 镜像基础:ubuntu:22.04
  • 软件版本:EmailRelay 2.6.1(源码包 emailrelay-2.6.1-src.tar.gz,SHA256 a0bcd88179a6545817b670b1ed6d6aac7c4dae90fabd220ef08d3f6743b69e9c)
  • 镜像体积:201MB(两阶段构建后)
  • 宿主端口规划:8131→relay 的 SMTP(25),8132→relay 的管理口(10027)
  • 编排项目名:emailrelay-2.6.1-stack;内网网段 10.89.72.0/24

二、镜像选型说明

Docker Hub 实查:EmailRelay 官方不发布任何容器镜像,也无维护活跃的社区镜像,因此本文采用「ubuntu:22.04 + 官方源码包编译」的自建镜像——版本与《EmailRelay 2.6.1 Ubuntu 22.04 安装指南》完全一致,升级只需改 ARG EMAILRELAY_VERSION。

三、前置条件

  • Docker Engine 与 Docker Compose v2 可用;
  • 宿主 8131/8132 端口空闲(ss -ltn | grep -E '8131|8132' 无输出);
  • 能访问 downloads.sourceforge.net(构建期下载源码包)。

四、部署步骤

4.1 项目结构

emailrelay-compose/
├── Dockerfile
└── docker-compose.yml

4.2 Dockerfile(两阶段:编译 → 运行时)

# syntax=docker/dockerfile:1
FROM ubuntu:22.04 AS build
ARG EMAILRELAY_VERSION=2.6.1
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      build-essential wget ca-certificates libssl-dev libpam0g-dev \
    && rm -rf /var/lib/apt/lists/*
RUN cd /tmp \
    && wget -q https://downloads.sourceforge.net/project/emailrelay/emailrelay/${EMAILRELAY_VERSION}/emailrelay-${EMAILRELAY_VERSION}-src.tar.gz \
    && tar -xzf emailrelay-${EMAILRELAY_VERSION}-src.tar.gz \
    && cd emailrelay-${EMAILRELAY_VERSION} \
    && ./configure --prefix=/usr/local \
    && make -j"$(nproc)" \
    && make install DESTDIR=/pkg

FROM ubuntu:22.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      libssl3 libpam0g ca-certificates \
    && rm -rf /var/lib/apt/lists/*
COPY --from=build /pkg/usr/local /usr/local
RUN mkdir -p /var/spool/emailrelay
EXPOSE 25
VOLUME ["/var/spool/emailrelay"]
ENTRYPOINT ["/usr/local/sbin/emailrelay"]
CMD ["--as-server", "--port", "25", "--spool-dir", "/var/spool/emailrelay", \
     "--remote-clients", "--log", "--log-time", "--no-daemon", "--user", "root"]

要点:构建阶段装编译链与 libssl-dev/libpam0g-dev(缺了就没有 TLS/PAM 能力),make install DESTDIR=/pkg 把产物收进独立目录;运行阶段只带 libssl3/libpam0g 运行库,COPY 成品,镜像 201MB。

4.3 docker-compose.yml

name: emailrelay-2.6.1-stack

networks:
  emailrelay-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.72.0/24

services:
  relay:
    build: .
    image: emailrelay:2.6.1
    container_name: emailrelay-2.6.1-relay
    hostname: mail-relay.internal
    networks:
      - emailrelay-net
    command:
      - "--log"
      - "--log-time"
      - "--verbose"
      - "--no-syslog"
      - "--forward-on-disconnect"
      - "--forward-to"
      - "sink:10026"
      - "--port"
      - "25"
      - "--spool-dir"
      - "/var/spool/emailrelay"
      - "--remote-clients"
      - "--no-daemon"
      - "--user"
      - "root"
      - "--admin"
      - "10027"
    ports:
      - "8131:25"
      - "8132:10027"
    volumes:
      - relay-spool:/var/spool/emailrelay

  sink:
    image: emailrelay:2.6.1
    container_name: emailrelay-2.6.1-sink
    hostname: upstream-sink.internal
    networks:
      - emailrelay-net
    command:
      - "--log"
      - "--log-time"
      - "--verbose"
      - "--no-syslog"
      - "--port"
      - "10026"
      - "--spool-dir"
      - "/var/spool/emailrelay"
      - "--remote-clients"
      - "--no-daemon"
      - "--user"
      - "root"
    volumes:
      - sink-spool:/var/spool/emailrelay

volumes:
  relay-spool:
  sink-spool:

⚠️ 关键写法:relay 没有直接用 --as-proxy sink:10026、sink 没有直接用 --as-server——这两个宏选项都隐含 --close-stderr(启动后关闭 stderr,日志只进 syslog),容器里没有 syslog 守护,docker logs 会一直空白。上表把它们展开为等价显式参数(--as-proxy = --log --close-stderr --forward-on-disconnect --forward-to,去掉 --close-stderr)并补 --no-syslog,日志即可经 stderr 被 docker logs/docker compose logs 捕获。sink 侧 --as-server = --log --close-stderr,展开后不加转发参数即为纯存储守护。

4.4 构建并启动

cd emailrelay-compose
docker compose build
docker compose up -d
docker compose ps

docker compose ps 实测回显:

NAME                     IMAGE              COMMAND                  SERVICE   CREATED          STATUS          PORTS
emailrelay-2.6.1-relay   emailrelay:2.6.1   "/usr/local/sbin/ema…"   relay     14 seconds ago   Up 14 seconds   0.0.0.0:8131->25/tcp, [::]:8131->25/tcp, 0.0.0.0:8132->10027/tcp, [::]:8132->10027/tcp
emailrelay-2.6.1-sink    emailrelay:2.6.1   "/usr/local/sbin/ema…"   sink      14 seconds ago   Up 13 seconds   25/tcp

⚠️ 启动顺序告警可忽略:relay 与 sink 同时拉起时,relay 首次解析 sink 可能早于 sink 注册完成,日志出现 warning: dns lookup of forward-to address failed: no such host: "sink" (temporary failure in name resolution)。EmailRelay 的转发是连接触发+可重试的,首封信到达时会重新解析,不影响功能。

五、端到端验证(实测回显)

5.1 经内网服务名发信(同网络内客户端视角)

$ swaks --server relay:25 --from alice@example.org --to bob@example.com \
    --h-Subject "Compose E2E Test" --body "hello from compose"
=== Trying relay:25...
=== Connected to relay.
<-  220 mail-relay.internal -- E-MailRelay V2.6.1 -- Service ready
 -> EHLO ec4b1b171ad8
<-  250-mail-relay.internal says hello
<-  250-VRFY
<-  250-PIPELINING
<-  250 8BITMIME
 -> MAIL FROM:<alice@example.org>
<-  250 sender <alice@example.org> OK
 -> RCPT TO:<bob@example.com>
<-  250 recipient <bob@example.com> OK
 -> DATA
<-  354 start mail input -- end with <CRLF>.<CRLF>
 -> (DATA 段发送行,此处略)
<-  250 message processed
 -> QUIT
<-  221 OK
=== Connection closed with remote host.

5.2 经宿主映射端口发信(外部客户端视角)

$ swaks --server 10.89.72.1 --port 8131 --from carol@example.org --to dave@example.net \
    --h-Subject "Published Port Test" --body "via 8131"
=== Trying 10.89.72.1:8131...
=== Connected to 10.89.72.1.
<-  220 mail-relay.internal -- E-MailRelay V2.6.1 -- Service ready
(EHLO 能力与 DATA 段与 4.1 一致,此处略)
<-  250 message processed

5.3 确认邮件到达 sink 队列

$ docker exec emailrelay-2.6.1-sink ls /var/spool/emailrelay/
emailrelay.1.1792907099.1.content
emailrelay.1.1792907099.1.envelope
emailrelay.1.1792907133.2.content
emailrelay.1.1792907133.2.envelope

5.4 管理接口查状态(宿主 8132)

$ printf 'status\nquit\n' | nc 10.89.72.1 8132
LISTEN: 0.0.0.0:25
LISTEN: 0.0.0.0:10027
LISTEN: ::.10027
LISTEN: ::.25
OUT started: 1
OUT finished: 1
IN started: 2
IN finished: 1

5.5 版本验证

$ docker exec emailrelay-2.6.1-relay /usr/local/sbin/emailrelay --version
E-MailRelay V2.6.1

Copyright (C) 2026 Graeme Walker

(尾部为 OpenSSL 声明与许可行,此处略。)

六、数据持久化说明

卷容器内路径内容
relay-spool/var/spool/emailrelayrelay 收信队列(content+envelope 文件对),转发成功即删
sink-spool/var/spool/emailrelaysink 落盘的邮件

队列即文件系统,备份=拷贝目录;docker compose down 不加 -v 时卷保留,重启后积压邮件不丢。

七、配置参数说明

compose command 数组逐条对应 EmailRelay 命令行选项(镜像 ENTRYPOINT 固定为 /usr/local/sbin/emailrelay,command 即其参数)。本栈用到的参数:

参数含义
--forward-to sink:10026下游 SMTP 地址(compose 内用服务名解析)
--forward-on-disconnect客户端断开后触发队列转发
--port 25 / --admin 10027SMTP 监听口 / 管理口
--remote-clients允许非本机客户端连接(容器间必须)
--spool-dir队列目录(挂卷持久化)
--no-daemon --user root前台运行(容器主进程必须),运行身份
--log --log-time --verbose --no-syslog日志走 stderr、带时间戳、info 级

要加 TLS/AUTH/过滤等更多参数,直接往 command 数组追加即可,完整参数体系见《EmailRelay 2.6.1 配置参数详解》。

八、配置文件挂载(可选)

EmailRelay 选项与配置文件键一一对应。若偏好配置文件而非长 command 数组:

# 宿主编写 emailrelay.conf(键名=去掉前导 -- 的选项名)
forward-to=sink:10026
forward-on-disconnect
port=25
spool-dir=/var/spool/emailrelay
remote-clients
no-daemon
user=root
admin=10027
log
log-time
verbose
no-syslog

compose 挂载并作为位置参数传入:

    volumes:
      - relay-spool:/var/spool/emailrelay
      - ./emailrelay.conf:/usr/local/etc/emailrelay.conf:ro
    command: ["/usr/local/etc/emailrelay.conf"]

九、等价 docker run

relay 单服务不编 sink 时,等价命令:

docker build -t emailrelay:2.6.1 .
docker network create --subnet 10.89.72.0/24 emailrelay-net
docker run -d --name emailrelay-2.6.1-relay --hostname mail-relay.internal \
  --network emailrelay-net -p 8131:25 -p 8132:10027 \
  -v relay-spool:/var/spool/emailrelay \
  emailrelay:2.6.1 \
  --log --log-time --verbose --no-syslog \
  --forward-on-disconnect --forward-to <下游主机>:10026 \
  --port 25 --spool-dir /var/spool/emailrelay \
  --remote-clients --no-daemon --user root --admin 10027

十、常用运维命令

docker compose logs -f relay        # 跟踪转发日志(须按本文展开宏选项才有输出)
docker compose ps                   # 查看两服务状态与端口映射
docker compose restart relay        # 重启转发服务
docker exec emailrelay-2.6.1-relay ls /var/spool/emailrelay   # 看 relay 积压
printf 'status\nquit\n' | nc 127.0.0.1 8132    # 管理口状态
printf 'forward\nquit\n' | nc 127.0.0.1 8132   # 手动触发队列转发
docker compose down                 # 停栈(卷保留);加 -v 连卷一起清

十一、常见问题

Q1:docker logs 一直空白? 用了 --as-proxy/--as-server 宏选项(隐含 --close-stderr),或漏了 --no-syslog。按本文 3.3 的展开写法修改 command 即可。

Q2:relay 日志报 dns lookup of forward-to address failed: no such host: "sink"? 启动竞态的正常现象(见 3.4 说明)。若持续报,检查两服务是否在同一 compose 网络、--forward-to 的主机名拼写。

Q3:外部客户端连 8131 被拒? 确认 --remote-clients 已加(默认只接受本机连接);宿主防火墙放行 8131。

Q4:改 Dockerfile 后镜像没更新? docker compose build 后需 docker compose up -d 重建容器;仅想换版本可 docker compose build --build-arg EMAILRELAY_VERSION=x.y.z(需把 ARG 传到 wget URL,本文 Dockerfile 已支持改 ARG 值重建)。

十二、参考链接