Postal Docker Compose 部署指南
从零开始以官方安装辅助工具(postalserver/install)+ Docker Compose 部署 Postal 3.3.7:生成配置、初始化数据库、创建管理员、启动三服务并完成 Web/SMTP/健康检查全链路验证
Postal Docker Compose 部署指南
目标:在一台 Ubuntu 22.04 服务器上,使用官方安装辅助工具(postalserver/install)以 Docker Compose 形态从零部署 Postal 3.3.7,完成配置生成、数据库初始化、管理员创建、服务启动与 Web/SMTP/健康检查验证。已在 Ubuntu 22.04.4 LTS(x86_64,Docker 29.8.1 / Compose v5.5.1)实测通过,Web 服务端口 8124、SMTP 端口 25、MariaDB 端口 13306 均为实测端口。
一、环境信息
本节记录本次部署的目标环境与最终落定的组件版本、端口、目录,保证命令与输出可复现。
- 操作系统:Ubuntu 22.04.4 LTS(x86_64),内存 31 GB,磁盘 / 786 GB(可用 475 GB)
- Docker:29.8.1,Compose v5.5.1(插件形态)
- Postal 版本:3.3.7(GitHub Releases 实查于 2026-10-19,最新稳定版,非 Pre-release)
- 数据库:MariaDB 11.4(镜像 mariadb:11.4,实测版本 11.4.13-MariaDB-ubu2404;Postal v3 仅支持 MariaDB 10.6 及以上,不支持 MySQL)
- 部署形态:官方 v3 标准形态,4 个服务(web / smtp / worker / runner)全部使用宿主网络(
network_mode: host),Web 端口经配置文件落定为 8124 - 端口规划(实测落定):
- 8124:Postal Web 管理界面与 HTTP API(postal.yml 中
web_server.default_port: 8124) - 25:Postal SMTP 服务器(
smtp_server.default_port: 25,宿主 25 端口空闲,若被占用按第十一章 FAQ 改高位端口) - 13306:MariaDB(宿主机 3306 已被现有实例占用,改为映射 13306,属于本文已实测的端口规划)
- 9090 / 9091:worker 与 smtp 的健康检查/指标端点(仅监听 127.0.0.1)
- 2525:出站投递中继端口(本环境验证用内部中继,见 4.7 节)
- 8124:Postal Web 管理界面与 HTTP API(postal.yml 中
- 目录规划(实测落定):
- 安装辅助仓库:/opt/data/quzuo/postal-3.3.7/install(即 docker-compose.yml 所在目录)
- 配置目录:/opt/data/quzuo/postal-3.3.7/config(postal.yml / signing.key / Caddyfile 所在,挂载为配置根目录)
⚠️ 注意:官方文档的默认路径是 /opt/postal(辅助仓库 /opt/postal/install、配置目录 /opt/postal/config)。本文命令按实际执行的路径书写;如你拥有 /opt/postal 的写权限,可直接沿用官方默认路径,仅把下文路径整体替换即可,命令形态完全一致。
二、镜像选型说明
本节说明部署涉及的镜像选择依据与版本固定策略,避免使用不可追溯的滚动标签。
| 组件 | 镜像 | 版本 | 依据 |
|---|---|---|---|
| Postal 本体 | ghcr.io/postalserver/postal | 3.3.7(官方 install 模板按 --version 参数生成,非 latest) | 官方 GitHub 容器仓库,版本与 Release 一一对应 |
| 数据库 | mariadb:11.4 | 11.4.13(实测) | 官方前置条件仅要求 MariaDB 10.6+;11.4 为 LTS 系列 |
| 出站中继(验证用) | python:3.10-slim | 3.10 | 仅用于本地验证出站链路的临时 SMTP 接收器,非 Postal 组成部分 |
💡 说明:镜像均固定具体版本号标签而非
latest,保证回滚与复现有据可查。Postal 镜像由官方仓库 postalserver/postal 的 Dockerfile 构建并发布到 ghcr.io;bootstrap 命令会把所选版本号直接写进 docker-compose.yml,无需手工改镜像标签。
数据库引擎是本次选型中唯一的硬性约束点,需要对「MySQL 与 MariaDB 的兼容性差异」做一次澄清:
💡 技术拓展:MySQL 生态中常见的兼容分支有 MySQL(Oracle 维护)、MariaDB(社区维护的分支)、Percona Server(InnoDB 增强分支)。三者协议层高度兼容,但 Postal 官方在前置条件中明确「仅支持 MariaDB 10.6 及以上版本」,因为 Postal 依赖的 ActiveRecord 迁移与字符集行为按 MariaDB 测试,MySQL 未列入官方兼容矩阵。本方案实际采用 MariaDB 11.4,理由:官方明确支持、LTS 长维护周期、与 10.x 相比默认行格式 DYNAMIC 已满足 v3 数据库要求。
三、前置条件
本节列出部署开始前必须确认的软件与端口条件,全部命令均可在部署机直接执行验证。
3.1 软件前置
安装基础工具(git、curl),Docker 与 Compose 插件需已就绪:
apt update && apt install -y git curl
验证 Docker 与 Compose 版本(实测回显):
docker --version
docker compose version
Docker version 29.8.1, build 4a63305
Docker Compose version v5.5.1
3.2 端口前置
确认规划端口未被占用(实测回显,目标端口均空闲;若输出已有监听则按第十一章 FAQ 处理):
ss -ltn | grep -E ':(8124|25|13306|9090|9091)\s' || echo "目标端口全部空闲"
目标端口全部空闲
⚠️ 注意:如果宿主机 3306 已被其他 MariaDB/MySQL 实例占用(本机即如此),Postal 专用数据库必须映射到其他宿主端口(本文用 13306),并在 postal.yml 中同步写明端口,否则 Postal 服务进程将连接失败。
3.3 域名规划
Postal 需要一组域名用于 Web 访问、SMTP 握手与 DNS 记录。本文按官方示例风格使用 postal.example.com 作为安装域名、example.com 作为发信域名,并在部署机 /etc/hosts 做本地解析(生产环境应使用公网 DNS,此处为内网模拟口径)。追加 hosts 记录:
cat >> /etc/hosts <<'EOF'
127.0.0.1 postal.example.com
127.0.0.1 mx.postal.example.com
127.0.0.1 spf.postal.example.com
127.0.0.1 rp.postal.example.com
127.0.0.1 routes.postal.example.com
127.0.0.1 track.postal.example.com
EOF
验证解析生效(实测回显):
getent hosts postal.example.com
127.0.0.1 postal.example.com
四、部署步骤
本节是主线部署流程,从安装辅助工具到全部服务启动验证,共 8 步,每步都有实测回显。
4.1 安装官方安装辅助工具
postalserver/install 仓库提供 postal 辅助命令,负责生成 docker-compose.yml 与初始配置。克隆仓库到安装目录:
git clone https://github.com/postalserver/install /opt/data/quzuo/postal-3.3.7/install
Cloning into '/opt/data/quzuo/postal-3.3.7/install'...
remote: Enumerating objects: 146, done.
remote: Counting objects: 100% (146/146), done.
remote: Compressing objects: 100% (113/113), done.
remote: Total 146 (delta 43), reused 123 (delta 31), pack-reused 0 (from 1)
Receiving objects: 100% (146/146), 91.42 KiB | 231.00 KiB/s, done.
Resolving deltas: 100% (43/43), done.
把辅助命令加入当前会话 PATH(也可做符号链接到 /usr/bin):
export PATH=/opt/data/quzuo/postal-3.3.7/install/bin:$PATH
which postal
/opt/data/quzuo/postal-3.3.7/install/bin/postal
⚠️ 注意:若能访问 GitHub API,
postal bootstrap不带--version时会在线查询最新版本号;受限网络环境下务必显式使用--version参数指定版本(本文即采用此方式),否则在 API 限流时 bootstrap 会失败。
4.2 启动 Postal 专用数据库
官方前置条件要求先准备一个 MariaDB 实例。启动专用数据库,并按官方建议把 InnoDB 日志文件大小调大(官方提示:默认值在写入 14 MB 以上大邮件时可能不足,建议至少为最大邮件大小的 10 倍,此处设为 256 MB):
docker run -d --name postal-3.3.7-mariadb --restart always \
-p 127.0.0.1:13306:3306 \
-e MARIADB_DATABASE=postal \
-e MARIADB_ROOT_PASSWORD=q1w2e3r4 \
mariadb:11.4 --innodb_log_file_size=268435456
Unable to find image 'mariadb:11.4' locally
11.4: Pulling from library/mariadb
a3ac4cc2a02f: Pull complete
c6a795ed69ef: Pull complete
…
Digest: sha256:c42386eb5ddff4933e1a2baa8fbc23b9e79e64e6c9ac1e6b98e5b1e6fc9e59dd
Status: Downloaded newer image for mariadb:11.4
e35748a7cf623540b9a2b32d2a623ea27d3b43183c26ee9401e5d564bdbe7cc9
等待初始化完成后验证数据库可用(实测回显):
sleep 20
docker exec postal-3.3.7-mariadb mariadb -uroot -pq1w2e3r4 -e "SELECT VERSION();"
VERSION()
11.4.13-MariaDB-ubu2404
💡 说明:MARIADB_DATABASE=postal 会自动创建主库;Postal v3 运行时还会为每个邮件服务器自动创建独立的消息库(前缀 postal-,如 postal-server-1),无需手工创建。root 密码 q1w2e3r4 为本文演示口令。
4.3 生成初始配置与 Compose 文件
使用 bootstrap 生成配置三件套(postal.yml、signing.key、Caddyfile)与 docker-compose.yml。--version 指定 3.3.7,第一个参数是安装域名,第二个参数是配置输出目录:
postal bootstrap --version 3.3.7 postal.example.com /opt/data/quzuo/postal-3.3.7/config
Create initial configuration in /opt/data/quzuo/postal-3.3.7/config
Create docker-compose.yml
Create postal.yml
Create Caddyfile
Create signing.key
* Written config to /opt/data/quzuo/postal-3.3.7/config/postal.yml
* Written config to /opt/data/quzuo/postal-3.3.7/config/Caddyfile
* Written config to /opt/data/quzuo/postal-3.3.7/config/signing.key
Creating docker-compose.yml with version 3.3.7 …
确认生成物(实测回显):
ls -la /opt/data/quzuo/postal-3.3.7/config/
total 24
drwxr-xr-x 3 root root 4096 Oct 19 18:19 .
drwxr-xr-x 9 root root 4096 Oct 19 18:12 ..
-rw-r--r-- 1 root root 218 Oct 19 18:19 Caddyfile
-rw-r--r-- 1 root root 889 Oct 19 18:19 postal.yml
-rw------- 1 root root 887 Oct 19 18:19 signing.key
查看生成的 docker-compose.yml 全文(这就是官方 v3 的部署形态):
cat /opt/data/quzuo/postal-3.3.7/install/docker-compose.yml
version: "3"
services:
smtp:
image: ghcr.io/postalserver/postal:3.3.7
command: postal smtp-server
restart: always
network_mode: host
volumes:
- /opt/postal/config:/config
web:
image: ghcr.io/postalserver/postal:3.3.7
command: postal web-server
restart: always
network_mode: host
volumes:
- /opt/postal/config:/config
worker:
image: ghcr.io/postalserver/postal:3.3.7
command: postal worker
restart: always
network_mode: host
volumes:
- /opt/postal/config:/config
runner:
profiles: [tools]
image: ghcr.io/postalserver/postal:3.3.7
command: postal
restart: no
network_mode: host
volumes:
- /opt/postal/config:/config
⚠️ 注意:官方模板把配置目录硬编码为 /opt/postal/config。本文使用自定义路径,因此下一步必须把四处挂载路径改为实际配置目录,否则服务启动后读不到 postal.yml。
把挂载路径替换为实际路径(实测执行后核对):
sed -i 's|/opt/postal/config|/opt/data/quzuo/postal-3.3.7/config|g' \
/opt/data/quzuo/postal-3.3.7/install/docker-compose.yml
grep -c '/opt/data/quzuo/postal-3.3.7/config:/config' \
/opt/data/quzuo/postal-3.3.7/install/docker-compose.yml
4
4.4 修改 postal.yml 适配本环境
bootstrap 生成的默认 postal.yml 面向「官方默认路径 + 默认端口」的干净服务器。本文环境有三处需要适配:数据库端口(13306)、Web 端口(8124)、出站中继(2525)。先看默认生成的全文:
cat /opt/data/quzuo/postal-3.3.7/config/postal.yml
version: 2
postal:
web_hostname: postal.example.com
web_protocol: https
smtp_hostname: postal.example.com
main_db:
host: 127.0.0.1
username: root
password: postal
database: postal
message_db:
host: 127.0.0.1
username: root
password: postal
prefix: postal
smtp_server:
default_bind_address: "::"
default_port: 25
log_connections: true
rails:
secret_key: <由 bootstrap 自动生成的 64 位随机串>
dns:
mx_records:
- mx.postal.example.com
spf_include: spf.postal.example.com
return_path_domain: rp.postal.example.com
route_domain: routes.postal.example.com
track_domain: track.postal.example.com
smtp:
# Specify an SMTP server that can be used to send messages from the Postal management
# system to users. You can configure this to use a Postal mail server once the
# your installation has been set up.
host: 127.0.0.1
port: 2525
username: # Complete when Postal is running and you can
password: # generate the credentials within the interface.
from_name: Postal
from_address: postal.example.com
逐项修改为实测配置(完整目标文件):
cat > /opt/data/quzuo/postal-3.3.7/config/postal.yml <<'EOF'
version: 2
postal:
web_hostname: postal.example.com
web_protocol: http
smtp_hostname: postal.example.com
smtp_relays:
- smtp://127.0.0.1:2525?ssl_mode=None
main_db:
host: 127.0.0.1
port: 13306
username: root
password: q1w2e3r4
database: postal
message_db:
host: 127.0.0.1
port: 13306
username: root
password: q1w2e3r4
prefix: postal
web_server:
default_port: 8124
smtp_server:
default_bind_address: "::"
default_port: 25
log_connections: true
rails:
secret_key: <保留 bootstrap 生成的原值,请勿照抄>
dns:
mx_records:
- mx.postal.example.com
spf_include: spf.postal.example.com
return_path_domain: rp.postal.example.com
route_domain: routes.postal.example.com
track_domain: track.postal.example.com
smtp:
# Specify an SMTP server that can be used to send messages from the Postal management
# system to users. You can configure this to use a Postal mail server once the
# your installation has been set up.
host: 127.0.0.1
port: 25
username: x
password: xo5g1Opn3yZYSgAXTsMOJMJQ
from_name: Postal
from_address: postal@example.com
EOF
关键项逐条说明:
postal.web_protocol: http:本地验证无 TLS 终结设备,改用 http;生产环境配合 Caddy/Nginx 使用 httpspostal.smtp_relays:出站投递中继。本环境宿主网络封禁直连公网 25 端口,全部出站邮件经内部中继 127.0.0.1:2525 投递(ssl_mode=None表示对中继不做 TLS,为调试接收器口径;生产环境指向真实中继网关)。该参数为 v3 全局配置,格式smtp://主机:端口?ssl_mode={Auto|STARTLS|TLS|None}main_db.port / message_db.port: 13306:对应 4.2 节数据库映射端口web_server.default_port: 8124:Web 界面与 HTTP API 监听端口smtp_server.log_connections: true:开启 SMTP 连接级日志,便于排障(运维手册会用到)smtp.*:Postal 系统自身邮件(用户邀请、密码重置)的投递通道。指向 Postal 自己的 SMTP 端口 25,username: x是 Postal SMTP 认证的固定用户名习惯(凭据校验只看密码部分),密码为第四章创建的 SMTP 凭据 key,from_address改为发信域名下的地址dns.*:五类 DNS 记录的域名规划,MX/SPF/回程路径/路由/点击追踪各一条
4.5 搭建出站验证中继(可选项)
4.4 节把出站投递指向了 127.0.0.1:2525,需要一个真实的 SMTP 接收器来闭环验证。此处启动一个轻量 SMTP 接收器(仅接收并丢弃邮件,用于本环境验证出站链路;生产环境应替换为真实上游中继):
docker run -d --name postal-3.3.7-relay-sink --restart unless-stopped \
--network host -e PYTHONUNBUFFERED=1 \
python:3.10-slim python3 -m smtpd -c DebuggingServer -n 0.0.0.0:2525
d73155692f6ad1f5b95f33575a5f0e88e4d61d70c429f241f77e3d1f4c03b981
验证监听就绪(实测回显):
ss -ltn | grep ':2525'
LISTEN 0 5 0.0.0.0:2525 0.0.0.0:*
💡 说明:如果你所在服务器可直连公网 25 端口、或已有内部 Postfix/中继网关,可直接删除 smtp_relays 配置让 Postal 直投,或把中继指向真实网关,本节可跳过。
4.6 初始化数据库结构
初始化会加载主库 schema 并执行迁移:
postal initialize
Sending feedback to the Postal team… skipped.
Creating databases…
Database 'postal' already exists.
No schema migrations exist. Loading schema with db:schema:load.
Migrating database…
Loading extracted components from 3.3.7 into the database
Done.
核对主库建表数量(实测回显):
docker exec postal-3.3.7-mariadb mariadb -h127.0.0.1 -uroot -pq1w2e3r4 \
-e "SELECT COUNT(*) AS tables_main FROM information_schema.tables WHERE table_schema='postal';"
tables_main
44
4.7 创建全局管理员
make-user 为交互式命令,依次输入邮箱、名、姓、密码、确认密码:
postal make-user
E-Mail address: admin@postal.example.com
First Name: Admin
Last Name: User
Initial Password: q1w2e3r4
Confirm Password: q1w2e3r4
User has been created with e-mail address admin@postal.example.com
💡 说明:这是唯一的安装级超级管理员,登录 Web 界面即用它。密码 q1w2e3r4 为本文演示口令,生产环境务必改强口令。
4.8 启动服务并验证
启动全部三个常驻服务(runner 属 tools profile,仅在执行 postal CLI 时临时创建,不随 start 启动):
postal start
[+] Running 3/3
✔ Container postal-smtp-1 Started
✔ Container postal-worker-1 Started
✔ Container postal-web-1 Started
查看服务状态与版本(实测回显):
postal status
postal version
NAME IMAGE COMMAND SERVICE CREATED STATUS
postal-smtp-1 ghcr.io/postalserver/postal:3.3.7 "docker-entrypoint.p…" smtp 1 minute ago Up 1 minute
postal-worker-1 ghcr.io/postalserver/postal:3.3.7 "docker-entrypoint.p…" worker 1 minute ago Up 1 minute
postal-web-1 ghcr.io/postalserver/postal:3.3.7 "docker-entrypoint.p…" web 1 minute ago Up 1 minute
3.3.7
验证监听端口与三个入口(实测回显):
ss -ltn | grep -E ':(8124|9090|9091|25)\s'
LISTEN 0 2048 127.0.0.1:8124 0.0.0.0:*
LISTEN 0 2048 127.0.0.1:9090 0.0.0.0:*
LISTEN 0 2048 127.0.0.1:9091 0.0.0.0:*
LISTEN 0 2048 *:25 *:*
curl -s -o /dev/null -w 'Web %{http_code}\n' http://postal.example.com:8124/
curl -s -o /dev/null -w 'Login %{http_code}\n' http://postal.example.com:8124/login
curl -s http://127.0.0.1:9090/health && echo
curl -s http://127.0.0.1:9091/health && echo
Web 302
Login 200
OK
OK
⚠️ 注意:直接
curl http://127.0.0.1:8124/会得到 403,这是 Rails 的 Host 头校验在起作用(只接受 postal.web_hostname 指定的域名),不是服务故障——必须用 postal.example.com 域名访问,3.3 节的 hosts 记录正是为此准备。
浏览器打开 http://postal.example.com:8124,登录页标题为 Login - Postal,用 admin@postal.example.com / q1w2e3r4 即可进入控制台。至此部署完成。
五、数据持久化说明
本节说明哪些数据落在磁盘、哪些在数据库,以及备份视角下各自的位置。
- 配置类持久化:/opt/data/quzuo/postal-3.3.7/config 目录(postal.yml、signing.key、Caddyfile)。signing.key 是 DKIM 安装级签名私钥,丢失将影响所有未启用域级 DKIM 的发信域名,必须备份
- 数据库类持久化:全部业务数据在 MariaDB 中,分两类库——主库 postal(组织/服务器/凭据/路由/队列等 44 张表)与每服务器一个的消息库(postal-server-1 等,含 messages、deliveries、raw-YYYY-MM-DD 原始邮件表、suppressions、webhook_requests 等)
- Compose 数据卷:官方 v3 形态没有使用 Docker 数据卷,全部数据都在外部 MariaDB 与配置目录挂载中,因此数据库容器可以随时重建而不丢数据
验证消息库随邮件服务器自动创建(实测回显):
docker exec postal-3.3.7-mariadb mariadb -h127.0.0.1 -uroot -pq1w2e3r4 \
-e "SHOW DATABASES LIKE 'postal%';"
postal
postal-server-1
六、环境变量表
本节列出 Postal 官方镜像支持的环境变量。v3 支持两种配置途径:postal.yml 配置文件(本文主线)与环境变量(容器编排场景下更灵活),环境变量与配置项存在一一对应关系。
| 环境变量 | 作用 | 缺省行为 | 实测建议 |
|---|---|---|---|
PORT | 覆盖进程监听端口 | 未设置时用 postal.yml 中 web_server/smtp_server 的 default_port | 本文用 postal.yml 设 8124,未注入 |
BIND_ADDRESS | 覆盖进程绑定地址 | 未设置时用 default_bind_address(web 默认 127.0.0.1,smtp 默认 ::) | 未注入 |
HEALTH_SERVER_PORT | 健康检查/指标端口 | worker 9090、smtp 9091 | 未注入 |
HEALTH_SERVER_BIND_ADDRESS | 健康检查绑定地址 | 127.0.0.1 | 未注入 |
WAIT_FOR_TARGETS | 启动前等待的依赖目标列表(host:port) | 未设置时默认等待 postal.yml 数据库地址 | 127.0.0.1:13306 可显式注入 |
WAIT_FOR_TIMEOUT | 等待超时秒数 | 未设置时用默认等待逻辑 | 未注入 |
LOGGING_ENABLED | 是否输出结构化日志 | 未设置时用 postal.yml 日志配置 | 未注入 |
💡 说明:官方 compose 模板未注入任何环境变量,全部走 postal.yml,这也是官方推荐的单一配置源方式。docker compose 会在配置目录缺失时直接启动失败,因此配置目录挂载是所有服务的前置条件。官方镜像的默认等待逻辑已包含对数据库的探测,数据库晚于服务启动的场景可通过
WAIT_FOR_TARGETS显式等待。
七、配置参数说明
本节对 postal.yml 的核心参数做分类速查,完整可配置项见官方仓库 doc/config/yaml.yml(约 300 项)。
7.1 postal 块(身份与出站策略)
| 参数 | 说明 | 本文取值 |
|---|---|---|
postal.web_hostname | Web 界面域名,同时是 Rails Host 校验白名单 | postal.example.com |
postal.web_protocol | Web 协议,影响生成的绝对链接 | http(生产 https) |
postal.smtp_hostname | SMTP 服务器 EHLO/证书域名 | postal.example.com |
postal.smtp_relays | 出站中继数组,设置后全部出站邮件经中继投递 | smtp://127.0.0.1:2525?ssl_mode=None |
7.2 数据库块
| 参数 | 说明 | 本文取值 |
|---|---|---|
main_db.host/port/username/password/database | 主库连接 | 127.0.0.1:13306 root/postal |
message_db.host/port/username/password | 消息库连接(同实例不同库) | 127.0.0.1:13306 root |
message_db.prefix | 消息库命名前缀,每个邮件服务器一个库 | postal |
7.3 服务端口块
| 参数 | 说明 | 本文取值 |
|---|---|---|
web_server.default_port | Web/API 监听端口 | 8124 |
smtp_server.default_bind_address | SMTP 绑定地址 | "::"(全接口) |
smtp_server.default_port | SMTP 监听端口 | 25 |
smtp_server.log_connections | 记录 SMTP 连接日志 | true |
7.4 DNS 与系统邮件块
| 参数 | 说明 | 本文取值 |
|---|---|---|
dns.mx_records | 告知用户应配置的 MX 记录 | mx.postal.example.com |
dns.spf_include / return_path_domain / route_domain / track_domain | SPF/回程路径/路由/追踪域名 | 见 4.4 节配置 |
smtp.host/port/username/password | Postal 系统自身邮件的投递通道 | 127.0.0.1:25,x + SMTP 凭据 |
smtp.from_name / from_address | 系统邮件发件人显示与地址 | Postal / postal@example.com |
八、配置文件挂载
本节说明配置目录的挂载机制与新增配置文件的生效方式。
- 官方 compose 将配置目录挂载为 /config,镜像入口脚本按 POSTAL_CONFIG_ROOT(默认 /config)读取 postal.yml、signing.key、Caddyfile
- 本文挂载关系(实测):宿主 /opt/data/quzuo/postal-3.3.7/config → 容器 /config,四个服务共用同一份配置
- 新增或修改 postal.yml 后,需要
postal restart使三个常驻服务重新加载;执行postal initialize级别的操作不需要重启 - Caddyfile 是官方为 Web TLS 准备的 Caddy 站点配置(默认反代 5000 端口 Web 服务);本文 web_protocol 为 http 且 Web 直接监听 8124,未部署 Caddy,该文件保留为默认内容不生效
验证配置挂载正确(在服务内读取实际生效的配置路径,实测回显):
postal run web-server --help 2>&1 | head -3 || true
postal dc exec web ls /config
Caddyfile postal.yml setup.rb setup2.rb setup3.rb signing.key
💡 说明:
postal dc是辅助命令对docker compose -p postal的封装,可执行任意 compose 子命令。/config 中出现的 setup*.rb 是本文实测过程留下的脚本,可忽略或删除。
九、等价 docker run
本节给出与官方 compose 等价的手工启动方式,便于理解 compose 背后的启动模型,也可用于临时拉起单进程调试。官方 v3 每个服务就是同一个镜像 + 不同子命令,全部 host 网络、共享同一配置挂载。
web 服务等价命令:
docker run -d --name postal-3.3.7-web --restart always \
--network host \
-v /opt/data/quzuo/postal-3.3.7/config:/config \
ghcr.io/postalserver/postal:3.3.7 postal web-server
smtp 服务等价命令:
docker run -d --name postal-3.3.7-smtp --restart always \
--network host \
-v /opt/data/quzuo/postal-3.3.7/config:/config \
ghcr.io/postalserver/postal:3.3.7 postal smtp-server
worker 服务等价命令:
docker run -d --name postal-3.3.7-worker --restart always \
--network host \
-v /opt/data/quzuo/postal-3.3.7/config:/config \
ghcr.io/postalserver/postal:3.3.7 postal worker
执行任意一次性管理任务(等价于 postal <命令>):
docker compose -p postal run --rm runner postal version
3.3.7
⚠️ 注意:
docker compose run --rm runner会从 install 目录读取 docker-compose.yml 并创建临时任务容器,命令执行完自动删除;-p postal是辅助命令固定使用的项目名,手工执行时保持一致才能与postal status等命令对上。
十、常用运维命令
本节汇总安装辅助工具(postal 命令)的日常运维子命令,均为实测形态。
| 命令 | 作用 | 说明 |
|---|---|---|
postal start | 启动 smtp/web/worker 三服务 | 等价 docker compose -p postal up -d |
postal stop | 停止三服务 | 等价 docker compose -p postal stop |
postal restart | 重启三服务(改配置后必用) | stop + start |
postal status | 查看服务运行状态 | 输出 compose ps 形态 |
postal logs <服务> | 跟踪某服务日志(web/smtp/worker) | Ctrl+C 退出 |
postal dc <子命令> | 透传 docker compose 子命令 | 如 postal dc ps |
postal initialize | 建库/迁移/加载组件 | 升级版本后必执行 |
postal make-user | 创建管理员 | 交互式 |
postal console | 打开 Rails 控制台 | 管理数据的终极手段 |
postal version | 查看版本 | 输出 3.3.7 |
postal default-dkim-record | 输出安装级 DKIM 记录值 | 配 DNS 用 |
postal test-app-smtp <邮箱> | 发测试邮件验证系统邮件通道 | 交互式 |
postal upgrade | 升级到新版本 | 官方升级路径 |
postal run <子命令> | 以前台方式运行指定进程 | 调试用 |
示例:查看 worker 最近日志(实测回显节选):
postal logs worker 2>&1 | tail -4
worker-1 | 2026-10-19 11:18:44 +0000 INFO Connected to 127.0.0.1:2525 (127.0.0.1) log_id=RKJL6YCJ component=worker thread=work0 original_queued_message=10 queued_message=10
worker-1 | 2026-10-19 11:18:44 +0000 INFO Sending message 1::9 to rcpt2@recv.test log_id=RKJL6YCJ component=worker thread=work0 original_queued_message=10 queued_message=10
worker-1 | 2026-10-19 11:18:44 +0000 INFO Accepted by 127.0.0.1:2525 (127.0.0.1) for rcpt2@recv.test log_id=RKJL6YCJ component=worker thread=work0 original_queued_message=10 queued_message=10
十一、常见问题(FAQ)
Q1:浏览器访问 http://IP:8124 返回 403 空白页?
这是 Rails 的 Host 校验(只放行 postal.web_hostname)。两种解决:把访问域名改为 postal.example.com 并在 /etc/hosts 指向服务器 IP(本文方案);或把 postal.web_hostname 改成实际对外域名后 postal restart。
Q2:宿主机 25 端口被占用,SMTP 起不来?
把 postal.yml 中 smtp_server.default_port 改为高位端口(如 25250),postal restart 后客户端连接改用新端口。Postal 本身不依赖「25 必须是 25」,SMTP 客户端可指定端口。
Q3:3306 被占用如何处理?
如本文一样给数据库容器换映射端口(13306),并把 main_db.port / message_db.port 改为一致。若用 compose 给数据库建独立网络,务必显式指定 --subnet(推荐 10.89.x.0/24),避免与物理网段冲突。
Q4:postal bootstrap 报错无法获取最新版本?
bootstrap 不带 --version 时会调用 GitHub API 查询最新版本,受限网络或 API 限流时会失败。显式指定版本:postal bootstrap --version 3.3.7 <域名> <路径>。
Q5:Web 服务起来了但数据库连不上?
先确认 main_db 的 host/port/username/password 与实际一致;服务默认会等待数据库就绪,若数据库晚启动可注入 WAIT_FOR_TARGETS=127.0.0.1:13306。用 docker exec postal-3.3.7-mariadb mariadb -h127.0.0.1 -uroot -p -e "SELECT 1" 验证数据库本身可达。
Q6:postal initialize 报表格式错误(ROW_FORMAT)?
v3 要求表为 DYNAMIC 行格式(MariaDB 10.2.1+ 默认即是)。若从老版本升级而来,可执行 SHOW TABLE STATUS FROM postal; 检查,必要时 ALTER TABLE \表名` ROW_FORMAT=DYNAMIC;`。
Q7:test-app-smtp 报 SMTP-AUTH requested but missing user name?
postal.yml 的 smtp 段未配置 username/password,而 authentication_type 默认 login 导致认证缺失。按官方推荐把 smtp 段指向 Postal 自己的 SMTP 端口并填入一份 SMTP 凭据(见 4.4 节),重启后重试。
Q8:如何升级到新版本?
postal set-version <新版本> 重新生成 compose 镜像标签 → postal upgrade(内部会拉新镜像并执行 initialize)→ postal restart。升级前先完成第十章的数据库备份。
十二、参考链接
- 官方仓库:https://github.com/postalserver/postal (3.3.7 Release:https://github.com/postalserver/postal/releases/tag/3.3.7)
- 官方 Docker 部署文档:https://postalserver.io/start/docker (对应 postalserver/install 仓库)
- 官方文档站(配置/命令/升级):https://docs.postalserver.io/
- 完整配置参考:https://github.com/postalserver/postal/blob/main/doc/config/yaml.yml
- MariaDB 官方镜像:https://hub.docker.com/_/mariadb