HyperKitty 1.3.12 Docker Compose 部署指南
Docker Compose 单文件部署 HyperKitty 1.3.12 完整归档栈:maxking 官方镜像选型、postgres+core+web+幂等建库作业四服务编排与逐项说明、首启初始化序列与双库/REST/归档 API 链路实测验证,附镜像升级、备份、卷回收与 8 条真实排障 FAQ。
HyperKitty 1.3.12 Docker Compose 部署指南
目标:用一份 Docker Compose 文件,基于官方
maxking/mailman-core/maxking/mailman-web镜像,单命令拉起 HyperKitty 1.3.12 邮件列表归档完整栈(PostgreSQL 双库 + Mailman core + HyperKitty/Postorius Web),覆盖首启初始化、健康检查、归档 API 链路验证、日常运维与真实排障,全部命令与回显实测于 2026-10-26。裸机安装见 《Debian 12 安装 HyperKitty 1.3.12 指南》。
一、环境信息
- Docker 引擎:
Docker version 29.9.2, build a82e528,编排工具Docker Compose version v5.6.1(实测回显) - 宿主机:Linux x86_64,工作目录
/opt/hyperkitty(本篇所有 compose 命令都在该目录执行) - 端口规划(宿主机侧,实测均可用):
| 宿主端口 | 容器端口 | 服务 | 用途 |
|---|---|---|---|
| 8142 | 8000 | mailman-web | HyperKitty/Postorius Web(HTTP 入口) |
| 8143 | 8001 | mailman-core | Mailman REST API(可选,仅管理用) |
容器内部还有两个不对外映射的端口:core 的 8024(LMTP,接 postfix 投递)与 web 的 8080(uwsgi 二进制协议 socket,预留给反向代理场景)。
- 网络与卷(实测
docker volume ls/docker network ls):
hyperkitty-1.3.12-core-var
hyperkitty-1.3.12-pgdata
hyperkitty-1.3.12-web-data
---
hyperkitty-1.3.12-net # bridge,固定子网 10.89.57.0/24
- 镜像内软件版本(容器内实查):web 容器含 HyperKitty 1.3.12、postorius 1.3.13、django-mailman3 1.3.15;core 容器 REST
/3.0/system/versions返回GNU Mailman 3.3.11b1 (Tom Sawyer)、Python 3.12.15——rolling 镜像比 PyPI 稳定线(3.3.10)略新,属官方滚动构建的正常现象。
二、镜像选型说明
官方维护的 Mailman 3 镜像由项目核心成员 maxking 发布在 Docker Hub,本方案用两个镜像组成栈:
| 镜像 | 当前本地 | 大小(实测) | 职责 |
|---|---|---|---|
| maxking/mailman-web:rolling | ID 51e649407244 | 425 MB | HyperKitty + Postorius 的 Django 工程(uwsgi 承载),含入口脚本自动初始化 |
| maxking/mailman-core:rolling | ID 5928461297a7 | 314 MB | Mailman core 引擎(REST 8001 / LMTP 8024),自动生成 mailman.cfg |
| postgres:16-alpine | — | 419 MB | 双数据库宿主(core 用 mailman 库、Web 用 mailmanweb 库) |
tag 策略:maxking 两个镜像同时维护 rolling(跟随 master 构建)与 0.5.x 系列定版 tag(2026-10-17 实查 Docker Hub,最新定版 0.5.2)。rolling 跟随 master 构建、更新频繁,本方案用 rolling 时已实查确认镜像内为 HyperKitty 1.3.12;生产如需锁定版本可改用 0.5.2 定版 tag,配置不变。
为什么必须 core + web 两个容器:Mailman 3 架构上「引擎与界面分离」,归档数据流是 core 收信 → 归档插件 HTTP 推送给 HyperKitty。两容器间通过内网主机名 mailman-core / mailman-web 互访,这正是 compose 网络的价值(详见 4.2/4.3 节的环境变量说明)。
三、部署规划与拓扑
宿主机
浏览器 ──8142──▶ [mailman-web 容器] HyperKitty/Postorius (http-socket 8000)
▲ │ ▲
REST 8001 │ │ │ 归档推送(Token 认证)
│ ▼ │
管理 ────8143──▶ [mailman-core 容器] Mailman core (REST 8001 / LMTP 8024)
│ │
▼ ▼
[postgres 容器] mailman 库(core) / mailmanweb 库(Web)
[hyperkitty-db-init 一次性任务] 建库幂等作业
| 服务 | 镜像 | 角色 | 关键点 |
|---|---|---|---|
| postgres | postgres:16-alpine | 双库存储 | healthcheck pg_isready,供 core/web 以 service_healthy 依赖 |
| mailman-core | maxking/mailman-core:rolling | 列表引擎 | 入口脚本按环境变量生成 /etc/mailman.cfg,归档插件随 HYPERKITTY_API_KEY 自动启用 |
| mailman-web | maxking/mailman-web:rolling | 归档 Web | 入口脚本自动 migrate/collectstatic/compress/建管理员,uwsgi 双端口 |
| hyperkitty-db-init | postgres:16-alpine | 一次性建库 | 幂等创建 mailmanweb 库(restart: "no",跑完即退出) |
四、compose 文件与逐段说明
4.1 文件全文
保存为 /opt/hyperkitty/docker-compose.yml(项目名 hyperkitty-1.3.12-stack 显式固定,保证容器/卷/网络命名稳定):
name: hyperkitty-1.3.12-stack
services:
postgres:
image: postgres:16-alpine
container_name: hyperkitty-1.3.12-postgres
restart: unless-stopped
environment:
POSTGRES_USER: mailman
POSTGRES_PASSWORD: q1w2e3r4
POSTGRES_DB: mailman
TZ: Asia/Shanghai
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- hyperkitty-1.3.12-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mailman -d mailman"]
interval: 10s
timeout: 5s
retries: 10
mailman-core:
image: maxking/mailman-core:rolling
container_name: hyperkitty-1.3.12-core
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://mailman:q1w2e3r4@postgres:5432/mailman
DATABASE_TYPE: postgres
DATABASE_CLASS: mailman.database.postgresql.PostgreSQLDatabase
HYPERKITTY_API_KEY: SecretArchiverAPIKey
HYPERKITTY_URL: http://mailman-web:8000/hyperkitty/
MM_HOSTNAME: mailman-core
MAILMAN_REST_USER: restadmin
MAILMAN_REST_PASSWORD: restpass
SMTP_HOST: mailman-core
TZ: Asia/Shanghai
ports:
- "8143:8001"
volumes:
- core-var:/opt/mailman/var
networks:
- hyperkitty-1.3.12-net
mailman-web:
image: maxking/mailman-web:rolling
container_name: hyperkitty-1.3.12-web
restart: unless-stopped
depends_on:
- mailman-core
environment:
DATABASE_URL: postgresql://mailman:q1w2e3r4@postgres:5432/mailmanweb
SECRET_KEY: hyperkitty-1.3.12-compose-secret-key-0123456789
HYPERKITTY_API_KEY: SecretArchiverAPIKey
MAILMAN_REST_URL: http://mailman-core:8001
MAILMAN_REST_USER: restadmin
MAILMAN_REST_PASSWORD: restpass
SERVE_FROM_DOMAIN: localhost
MAILMAN_ADMIN_USER: admin
MAILMAN_ADMIN_EMAIL: admin@example.com
DJANGO_ALLOWED_HOSTS: localhost,127.0.0.1
TZ: Asia/Shanghai
ports:
- "8142:8000"
volumes:
- web-data:/opt/mailman-web-data
networks:
- hyperkitty-1.3.12-net
hyperkitty-db-init:
image: postgres:16-alpine
container_name: hyperkitty-1.3.12-dbinit
depends_on:
postgres:
condition: service_healthy
environment:
PGPASSWORD: q1w2e3r4
entrypoint: >
sh -c "psql -h postgres -U mailman -d mailman -tAc
\"SELECT 1 FROM pg_database WHERE datname='mailmanweb'\" | grep -q 1 ||
psql -h postgres -U mailman -d mailman -c 'CREATE DATABASE mailmanweb OWNER mailman;'"
networks:
- hyperkitty-1.3.12-net
restart: "no"
volumes:
pgdata:
name: hyperkitty-1.3.12-pgdata
core-var:
name: hyperkitty-1.3.12-core-var
web-data:
name: hyperkitty-1.3.12-web-data
networks:
hyperkitty-1.3.12-net:
name: hyperkitty-1.3.12-net
driver: bridge
ipam:
config:
- subnet: 10.89.57.0/24
4.2 core 服务逐项说明
DATABASE_URL+DATABASE_TYPE+DATABASE_CLASS:三件套决定 core 的数据库。DATABASE_CLASS必须显式给:入口脚本生成 mailman.cfg 时会用它写[database] class=行,缺省时生成空值,core 启动直接抛ValueError: Empty module name崩溃循环(FAQ 7.2,实测踩坑);HYPERKITTY_API_KEY:触发归档插件自动启用——入口脚本检测到该变量就在生成的 mailman.cfg 里写入[archiver.hyperkitty]段并生成/etc/mailman-hyperkitty.cfg(base_url 取自HYPERKITTY_URL,api_key 取自HYPERKITTY_API_KEY)。实测生成的两段配置:
[archiver.hyperkitty]
class: mailman_hyperkitty.Archiver
enable: yes
configuration: /etc/mailman-hyperkitty.cfg
[general]
base_url: http://mailman-web:8000/hyperkitty/
api_key: SecretArchiverAPIKey
HYPERKITTY_URL:归档推送目标,指向 web 容器的 http-socket(容器内 8000,不是 8080);MAILMAN_REST_USER/PASSWORD:生成的[webservice]凭据,web 容器侧三个MAILMAN_REST_*变量必须与之成对;SMTP_HOST:core 对外发信的 SMTP 服务器地址(容器内指向自身,生产可换外部中继);core-var卷:core 的/opt/mailman/var(队列、日志、锁、归档 spool)落持久卷,重建容器不丢列表数据。
4.3 web 服务逐项说明
DATABASE_URL:指向mailmanweb库(由 hyperkitty-db-init 幂等创建),与 core 的mailman库分离;SECRET_KEY:必须显式固定——不设时入口脚本每次启动随机生成,容器重启后所有登录会话/重置链接失效;HYPERKITTY_API_KEY:与 core 侧同一把令牌,HyperKitty 用它校验归档推送的Authorization: Token头;MAILMAN_ADMIN_USER/EMAIL:首启自动创建 Django 超管(只建号不设密码,首次登录前按 5.4 步设置口令);SERVE_FROM_DOMAIN+DJANGO_ALLOWED_HOSTS:对外域名与 Host 白名单,成对修改;web-data卷:挂到/opt/mailman-web-data,实测落盘内容为diskcache / fulltext_index / logs / static(静态产物与全文索引持久化,重建容器免重算);- ⚠️ 不要设置
MAILMAN_HOST_IP:镜像 settings.py 的逻辑是「该变量一旦非空就原样进入MAILMAN_ARCHIVER_FROM来源白名单」,填主机名mailman-core时白名单里是字符串而非 IP,归档推送会对所有人 403(FAQ 7.3,实测踩坑);留空时镜像才会自动解析 core 容器 IP。
4.4 postgres 与 db-init 逐项说明
- healthcheck
pg_isready+depends_on.condition: service_healthy:保证 core/web 起来前数据库已可接受连接(Django migrate 与 core 初始化都需要库就绪); hyperkitty-db-init:一次性作业容器。先查pg_database里有没有mailmanweb,没有才CREATE DATABASE,天然幂等——docker compose up反复执行安全,状态稳定为Exited (0)(见 5.2 实测)。
五、部署步骤与验证
5.1 拉起栈
mkdir -p /opt/hyperkitty && cd /opt/hyperkitty
# 将 4.1 节内容保存为 docker-compose.yml
docker compose up -d
输出节选(实测,首次创建):
[+] Running 7/7
✔ Network hyperkitty-1.3.12-net Created
✔ Volume "hyperkitty-1.3.12-pgdata" Created
✔ Volume "hyperkitty-1.3.12-core-var" Created
✔ Volume "hyperkitty-1.3.12-web-data" Created
✔ Container hyperkitty-1.3.12-postgres Healthy
✔ Container hyperkitty-1.3.12-core Started
✔ Container hyperkitty-1.3.12-web Started
5.2 状态确认
docker compose ps -a
NAME STATUS
hyperkitty-1.3.12-core Up 23 minutes
hyperkitty-1.3.12-dbinit Exited (0) 23 minutes ago
hyperkitty-1.3.12-postgres Up 4 hours (healthy)
hyperkitty-1.3.12-web Up 23 minutes
关键判据:postgres healthy;dbinit Exited (0)(幂等建库完成即退出,不是故障);core/web Up。镜像与体积核对:
docker compose images
CONTAINER REPOSITORY TAG PLATFORM IMAGE ID SIZE CREATED
hyperkitty-1.3.12-core maxking/mailman-core rolling linux/amd64 5928461297a7 314MB 9 days ago
hyperkitty-1.3.12-dbinit postgres 16-alpine linux/amd64 f46107955874 419MB 4 days ago
hyperkitty-1.3.12-postgres postgres 16-alpine linux/amd64 f46107955874 419MB 4 days ago
hyperkitty-1.3.12-web maxking/mailman-web rolling linux/amd64 51e649407244 425MB 9 days ago
5.3 首启初始化序列(实测日志)
web 容器入口脚本首次启动时自动完成建表、静态资源、压缩与管理员创建(docker logs hyperkitty-1.3.12-web 节选):
Applying sites.0002_alter_domain_unique... OK
Applying socialaccount.0004_app_provider_id_settings... OK
Applying socialaccount.0005_socialtoken_nullable_app... OK
Applying socialaccount.0006_alter_socialaccount_extra_data... OK
Creating admin user admin ...
Superuser created successfully.
Setting localhost as the default domain ...
[uWSGI] getting INI configuration from /opt/mailman-web/uwsgi.ini
(第二次起跑会跳过 migrate 直接进入 uwsgi 行;web 容器的 uwsgi 同时监听 8000(HTTP,对外)与 8080(uwsgi 协议,供反代),并内置 django-q 定时任务进程。)
5.4 设置管理员口令
MAILMAN_ADMIN_USER 只建号不设密。用 docker exec 进容器以 Django shell 设口令(演示约定 admin / q1w2e3r4):
docker exec hyperkitty-1.3.12-web python -c "
import os, django
os.environ.setdefault('DJANGO_SETTINGS_MODULE','settings')
django.setup()
from django.contrib.auth.models import User
u = User.objects.get(username='admin')
u.set_password('q1w2e3r4')
u.save()
print('password set for', u.username, 'is_superuser=', u.is_superuser)
"
password set for admin is_superuser= True
5.5 数据库与 Web/REST 验证
双库确认(实测回显):
docker exec hyperkitty-1.3.12-postgres psql -U mailman -d mailman \
-tAc "SELECT datname FROM pg_database WHERE datname IN ('mailman','mailmanweb') ORDER BY 1"
mailman
mailmanweb
Web 入口(宿主机 → 8142 → 容器 8000):根路径 301 到 Postorius 列表页,HyperKitty 归档首页 200:
curl -sI http://127.0.0.1:8142/ | head -3
curl -s -o /dev/null -w 'hyperkitty home: %{http_code}\n' http://127.0.0.1:8142/hyperkitty/
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: /mailman3/lists/
hyperkitty home: 200
core REST(宿主机 → 8143 → 容器 8001):
curl -s -u restadmin:restpass http://127.0.0.1:8143/3.0/system/versions
{"mailman_version": "GNU Mailman 3.3.11b1 (Tom Sawyer)", "python_version": "3.12.15 (main, Oct 1 2026, 13:38:06) [GCC 15.2.0]", "api_version": "3.0", "self_link": "http://mailman-core:8001/3.0/system/versions", "http_etag": "\"163e423af0427a156744135e2e44721bb301ce08\""}
5.6 归档 API 链路验证(白名单语义)
归档 API 带令牌访问——必须从 core 容器内发起(来源 IP 白名单只放行 core,见 FAQ 7.3):
docker exec hyperkitty-1.3.12-core curl -s -o /dev/null \
-w 'archive api from core: %{http_code}\n' \
'http://mailman-web:8000/hyperkitty/api/mailman/urls?mlist=testlist@example.com' \
-H 'Authorization: Token SecretArchiverAPIKey'
archive api from core: 200
同一请求改从宿主机经 8142 发起,应得到 403——这是白名单在正确工作(非 core 来源一律拒绝),不是故障:
archive api via 8142: 403
至此归档链路三个断面全部验证:core 侧插件配置自动生成(4.2 回显)、core→web Token 认证 200、Web 页面 200。浏览器访问 http://<宿主机>:8142/,用 admin / q1w2e3r4 登录即可管理列表与浏览归档。
六、Stack 生命周期运维
cd /opt/hyperkitty
# 停止 / 启动 / 重启(数据都在卷里,安全)
docker compose stop
docker compose start
docker compose restart mailman-web
# 日志跟踪(观察首启初始化或排障)
docker compose logs -f mailman-web
# 升级镜像(rolling 跟随上游,建议先备份再拉)
docker compose pull && docker compose up -d
# 备份:数据库转储 + 三卷打包
docker exec hyperkitty-1.3.12-postgres \
pg_dump -U mailman -d mailmanweb > mailmanweb-$(date +%F).sql
docker exec hyperkitty-1.3.12-postgres \
pg_dump -U mailman -d mailman > mailman-$(date +%F).sql
docker run --rm -v hyperkitty-1.3.12-web-data:/data -v $PWD:/backup alpine \
tar czf /backup/web-data.tgz -C /data .
docker run --rm -v hyperkitty-1.3.12-core-var:/data -v $PWD:/backup alpine \
tar czf /backup/core-var.tgz -C /data .
# 拆栈(保留卷与网络,数据不丢)
docker compose down
# 彻底删除(含数据卷,不可逆,先确认已备份)
docker compose down -v
升级注意:rolling tag 会拿到新的 HyperKitty/Postorius 版本,up -d 重建容器时入口脚本自动跑增量 migrate;回滚直接把 yml 里的 tag 换成 0.5.2 定版再 up -d。
七、常见问题(FAQ)
Q1:docker compose up 报 Pool overlaps with other one on this address space?
compose 网络的固定子网(10.89.57.0/24)与宿主机上其他 bridge 网络冲突。docker network ls 找到占用网络,确认无用后 docker network rm <name> 释放,或改 yml 里 subnet 为其他未占用网段后重新 up。
Q2:core 容器反复重启,日志 ValueError: Empty module name?
入口脚本生成的 mailman.cfg 里 [database] class= 行为空——compose 没给 DATABASE_CLASS。按 4.1 节补上 DATABASE_CLASS: mailman.database.postgresql.PostgreSQLDatabase 后 docker compose up -d 重建 core。实测修复后 core 稳定运行、REST 正常响应。
Q3:归档 API 始终 403(连 core 容器内 curl 也 403)?
根因:web 服务的环境变量里设置了 MAILMAN_HOST_IP。镜像 settings.py 的写法是 MAILMAN_ARCHIVER_FROM = (os.environ.get('MAILMAN_HOST_IP', gethostbyname(...)),)——变量一旦非空就原样进入来源白名单,填主机名 mailman-core 得到的是字符串元组,与请求来源 IP 永远匹配不上。解决:删掉该环境变量让镜像自动解析 core 的容器 IP(本文 4.1 即最终正确形态)。验证:修复后从 core 容器内带令牌请求 http://mailman-web:8000/hyperkitty/api/mailman/urls 得 200(5.6 实测),从宿主机经 8142 得 403 属白名单正确防护。另外 web 容器重启后若 core 的容器 IP 发生变化,需重启 web 让白名单重新解析。
Q4:web 容器退出,日志报无法解析 mailman-core?
web 初始化依赖 core 的 REST 可达,core 起不来时 web 会因 DNS/连接失败退出。先 docker compose ps -a 看 core 状态:若 core 在崩溃循环,按 Q2 修 DATABASE_CLASS;core 正常后 docker compose up -d 自动重建 web 即恢复(restart: unless-stopped 也会自行重试)。
Q5:MAILMAN_ADMIN_USER 建的 admin 登录不了?
该变量只创建账号不设密码。按 5.4 的 docker exec + Django shell set_password 方式设置口令(实测回显 password set for admin is_superuser= True),生产环境请改用强口令。
Q6:宿主端口 8142/8143 被占用?
宿主机端口冲突会在 up 时直接报 bind ... address already in use。改 yml 里 ports 的宿主侧端口(如 "8142:8000" 改 "9142:8000")后重新 up;容器内端口 8000/8001 不要动(core 插件的 HYPERKITTY_URL 与 web 的 MAILMAN_REST_URL 都按容器内端口写死指向主机名)。
Q7:容器重启后用户全部掉线?
没有固定 SECRET_KEY 时入口脚本每次启动随机生成,旧会话全部失效。按 4.3 说明在 yml 里显式写死 SECRET_KEY(长随机串),再 up -d 重建 web 生效。
Q8:想覆盖 Django 配置(改邮件后端、加站点域名等)?
镜像支持自定义 settings:在 web-data 卷对应的宿主目录放 settings_local.py(挂载点 /opt/mailman-web-data/),入口脚本会将其并入 Django 配置,升级镜像不丢失。改完 docker compose restart mailman-web 生效。
八、参考链接
- HyperKitty 官方文档:https://hyperkitty.readthedocs.io/en/latest/
- Mailman 3 官方容器文档(maxking 镜像 README):https://github.com/maxking/docker-mailman
- Docker Compose 官方手册:https://docs.docker.com/compose/
- Mailman core 文档(REST API / LMTP):https://mailman.readthedocs.io/en/latest/
- 站内参考:《HyperKitty 1.3.12 介绍与选型指南》、《Debian 12 安装 HyperKitty 1.3.12 指南》、《Mailman 3.3.10 介绍与选型指南》
(实测环境:Docker 29.9.2 + Compose v5.6.1,全部命令与回显采集日期 2026-10-26)