Postal Docker Compose 部署指南

从零开始以官方安装辅助工具(postalserver/install)+ Docker Compose 部署 Postal 3.3.7:生成配置、初始化数据库、创建管理员、启动三服务并完成 Web/SMTP/健康检查全链路验证

Postal邮件服务器Docker Compose2026/10/19

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 节)
  • 目录规划(实测落定):
    • 安装辅助仓库:/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/postal3.3.7(官方 install 模板按 --version 参数生成,非 latest)官方 GitHub 容器仓库,版本与 Release 一一对应
数据库mariadb:11.411.4.13(实测)官方前置条件仅要求 MariaDB 10.6+;11.4 为 LTS 系列
出站中继(验证用)python:3.10-slim3.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 使用 https
  • postal.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_hostnameWeb 界面域名,同时是 Rails Host 校验白名单postal.example.com
postal.web_protocolWeb 协议,影响生成的绝对链接http(生产 https)
postal.smtp_hostnameSMTP 服务器 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_portWeb/API 监听端口8124
smtp_server.default_bind_addressSMTP 绑定地址"::"(全接口)
smtp_server.default_portSMTP 监听端口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_domainSPF/回程路径/路由/追踪域名见 4.4 节配置
smtp.host/port/username/passwordPostal 系统自身邮件的投递通道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。升级前先完成第十章的数据库备份。

十二、参考链接