Keila 0.30.3 Docker Compose 部署指南

使用官方镜像 pentacent/keila:0.30.3 与 Docker Compose 部署 Keila:镜像核对、三服务编排(应用+PostgreSQL+Mailpit)、管理员初始化、登录与发信验证、日常运维。

KeilaKeila邮件服务邮件列表PostgreSQL2026/10/26

Keila 0.30.3 Docker Compose 部署指南

一、方案说明

Keila 官方提供打包镜像 pentacent/keila,是除源码构建外唯一的开箱即用部署形态(源码构建路线见《Debian 12 安装 Keila 0.30.3 指南》)。本文用 Docker Compose 编排三个服务完成一次完整部署:

服务镜像作用
keilapentacent/keila:0.30.3Keila 应用本体(内嵌 Oban 队列)
postgrespostgres:16-alpine唯一外部依赖,存储全部业务数据与作业队列
mailpitaxllent/mailpit:latest测试收信端,验证发信链路

Compose 版本提示:本文验证环境的 Compose 为 v2 系列(docker compose 子命令)。老式 docker-compose v1 不支持 Compose 文件顶层的 name 字段,建议升级到 v2 后操作。

二、镜像版本核对

写作时官方镜像与源码同步发布的最新 tag 为 0.30.3。拉取并核对:

docker pull pentacent/keila:0.30.3

实测回显(镜像已在本地时状态行简化为镜像全名):

docker.io/pentacent/keila:0.30.3

核对镜像元信息:

docker image ls pentacent/keila --format '{{.Repository}}:{{.Tag}}  {{.ID}}  {{.Size}}'
docker inspect pentacent/keila:0.30.3 --format 'Created: {{.Created}}{{"\n"}}Entrypoint: {{json .Config.Entrypoint}}{{"\n"}}Cmd: {{json .Config.Cmd}}{{"\n"}}ExposedPort: {{json .Config.ExposedPorts}}'

实测回显:

pentacent/keila:0.30.3  1c880b8946ef  472MB
Created: 2026-09-06T09:29:07.292635837Z
Entrypoint: ["/opt/app/bin/keila"]
Cmd: ["start"]
ExposedPort: {"4000/tcp":{}}

要点:镜像内置入口为 /opt/app/bin/keila,默认命令 start(前台运行并自动执行数据库迁移与初始化);应用监听 4000 端口;镜像基于 Alpine,体积约 472MB(解压后)。

不建议使用 pentacent/keila:latest 部署生产:floating tag 会在上游发版后指向未知新版本,升级不可控。固定版本 tag 才能保证重跑 docker compose up -d 时行为一致。

三、网络与目录准备

实验环境将本栈接入专用网络段(多组件共用时便于互相寻址),并准备数据目录:

docker network create keila-net --subnet 10.89.60.0/24 2>/dev/null; \
mkdir -p /opt/keila/pgdata /opt/keila/uploads

若本栈独占一台主机,可省略自定义网络,让 Compose 自动创建默认网络(删去编排文件中的 networks 声明即可)。

四、Compose 编排文件

写入 /opt/keila/docker-compose.yml:

name: keila-0.30.3-stack

services:
  keila:
    image: pentacent/keila:0.30.3
    container_name: keila-0.30.3-web
    restart: unless-stopped
    ports:
      - "8137:4000"
    environment:
      SECRET_KEY_BASE: "3bYcNOSrXrBns3jcLQv8CXiMHCOB/TPvsEIOOfJy8pJ/HQh3Ty5pyHtsJkbFlF/X"
      DB_URL: "postgres://keila:q1w2e3r4@postgres:5432/keila"
      URL_HOST: "192.168.146.131:8137"
      URL_SCHEMA: "http"
      MAILER_SMTP_HOST: "mailpit"
      MAILER_SMTP_PORT: "1025"
      MAILER_SMTP_FROM_EMAIL: "newsletter@keila.local"
      MAILER_SMTP_USER: "newsletter@keila.local"
      MAILER_SMTP_PASSWORD: "unused"
      MAILER_SMTP_AUTH_METHOD: "none"
      MAILER_SMTP_TLS_MODE: "none"
      KEILA_USER: "admin@keila.local"
      KEILA_PASSWORD: "q1w2e3r4q1"
      USER_CONTENT_DIR: "/opt/app/uploads"
    volumes:
      - ./uploads:/opt/app/uploads
    depends_on:
      - postgres
    networks:
      - keila-net

  postgres:
    image: postgres:16-alpine
    container_name: keila-0.30.3-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: keila
      POSTGRES_PASSWORD: q1w2e3r4
      POSTGRES_DB: keila
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    networks:
      - keila-net

  mailpit:
    image: axllent/mailpit:latest
    container_name: keila-0.30.3-mailpit
    restart: unless-stopped
    ports:
      - "8138:8025"
    networks:
      - keila-net

networks:
  keila-net:
    external: true

环境变量说明(完整清单见《Keila 0.30.3 配置参数详解》):

  • SECRET_KEY_BASE:至少 64 字符的会话签名密钥,docker run --rm alpine/openssl rand -base64 48 或 openssl rand -base64 48 生成后固化进文件,切勿每次部署更换;
  • DB_URL:指向同网络的 postgres 服务。POSTGRES_USER/PASSWORD/DB 三个变量与之一一对应;
  • URL_HOST:对外访问地址(用于生成邮件中的链接)。宿主机映射端口为 8137 时要写成 IP:8137 全拼;
  • MAILER_SMTP_*:系统邮件通道。本文指向同网络的 mailpit(AUTH_METHOD=none + TLS_MODE=none);生产环境改为真实 SMTP 中继;
  • KEILA_USER / KEILA_PASSWORD:首次启动自动创建的实例管理员(密码最少 10 个字符,故用 q1w2e3r4q1);
  • USER_CONTENT_DIR:联系人头像、Campaign 附件等用户内容的存储目录,映射到宿主机持久化。

name: keila-0.30.3-stack 显式声明栈名。注意 Compose v2 会把名字规范化(点号转为连字符),docker compose config 显示的实际栈名为 keila-0303-stack。

五、部署与初始化

cd /opt/keila && docker compose up -d

首次执行会拉取 postgres:16-alpine 与 axllent/mailpit:latest 后启动三个服务。确认状态:

docker compose ps --format '{{.Name}}\t{{.Image}}\t{{.Status}}'

实测回显:

keila-0.30.3-mailpit	axllent/mailpit:latest	Up 11 minutes (healthy)
keila-0.30.3-postgres	postgres:16-alpine	Up 11 minutes
keila-0.30.3-web	pentacent/keila:0.30.3	Up 11 minutes

Keila 容器首次启动会自动完成两件事:数据库迁移(约 30 张表)与管理员创建。观察日志:

docker logs keila-0.30.3-web 2>&1 | grep -E "Running|create table" | head -8

实测回显:

2026-10-26T06:33:41.490 [info] Loading 119 CA(s) from :otp store
2026-10-26T06:33:42.061 [info] == Running 20201229080000 Keila.Repo.Migrations.Auth.change/0 forward
2026-10-26T06:33:42.062 [info] create table users
2026-10-26T06:33:42.066 [info] create index users_email_index
2026-10-26T06:33:42.068 [info] create table tokens
2026-10-26T06:33:42.071 [info] create table groups
2026-10-26T06:33:42.074 [info] create table roles
2026-10-26T06:33:42.076 [info] create table permissions

日志尾部出现 Created root user with admin@keila.local 即管理员初始化完成。日常重启不会重复初始化(种子只在库为空时执行),KEILA_USER/KEILA_PASSWORD 只在首次启动前有意义。

六、功能验证

6.1 Web 访问与登录

curl -s -o /dev/null -w 'GET http://192.168.146.131:8137/ -> HTTP %{http_code} redirect=%{redirect_url}\n' http://192.168.146.131:8137/

实测回显:

GET http://192.168.146.131:8137/ -> HTTP 302 redirect=http://192.168.146.131:8137/auth/login

浏览器打开 http://192.168.146.131:8137,用 admin@keila.local / q1w2e3r4q1 登录(密码重置邮件等系统邮件由 MAILER_SMTP_* 通道发出)。命令行等价验证:

curl -s -c ck.txt -o /dev/null http://localhost:8137/auth/login
CSRF=$(curl -s -b ck.txt -c ck.txt http://localhost:8137/auth/login \
  | grep -oE 'name="_csrf_token"[^>]*' | grep -oE 'value="[^"]+"' | sed 's/value="//;s/"//')
curl -s -b ck.txt -c ck.txt -o /dev/null \
  -w 'POST /auth/login -> %{http_code} redirect=%{redirect_url}\n' \
  --data-urlencode "user[email]=admin@keila.local" \
  --data-urlencode "user[password]=q1w2e3r4q1" \
  --data-urlencode "_csrf_token=$CSRF" \
  http://localhost:8137/auth/login

实测回显:

POST /auth/login -> 302 redirect=http://localhost:8137/

6.2 发信链路验证

Keila 的系统邮件(注册激活、密码重置)走 MAILER_SMTP_* 通道。用未登录会话提交一次密码重置表单触发一封系统邮件:

curl -s -c rk.txt -o /dev/null http://localhost:8137/auth/reset
CSRF=$(curl -s -b rk.txt -c rk.txt http://localhost:8137/auth/reset \
  | grep -oE 'name="_csrf_token"[^>]*' | grep -oE 'value="[^"]+"' | sed 's/value="//;s/"//')
curl -s -b rk.txt -o /dev/null -w 'POST /auth/reset -> %{http_code}\n' \
  --data-urlencode "user[email]=admin@keila.local" \
  --data-urlencode "_csrf_token=$CSRF" \
  http://localhost:8137/auth/reset

实测回显:

POST /auth/reset -> 200

随后查询 mailpit 的 HTTP API(Web 界面为 http://192.168.146.131:8138):

curl -s http://localhost:8138/api/v1/messages \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print('mailpit total:', d['total']); [print(m['From']['Address'], '->', [a['Address'] for a in m['To']], '|', m['Subject']) for m in d['messages'][:3]]"

实测回显(执行两次重置后共 2 封):

mailpit total: 2
newsletter@keila.local -> ['admin@keila.local'] | Your Account Reset Link
newsletter@keila.local -> ['admin@keila.local'] | Your Account Reset Link

重置邮件经 Keila → mailpit(SMTP 1025)完整送达,发信链路验证通过。生产环境中把 MAILER_SMTP_* 换成真实中继后,联系人收到的 Campaign 邮件走同一条链路。

七、日常管理

# 查看应用日志(Keila 使用标准输出,docker logs 即可)
docker compose logs -f keila

# 重启单个服务
docker compose restart keila

实测回显:

 Container keila-0.30.3-web Restarting 
 Container keila-0.30.3-web Started 
# 修改编排文件后重新应用(仅重建配置变化的容器,数据不受影响)
docker compose up -d

实测回显(配置无变化时全部保持 Running):

 Container keila-0.30.3-mailpit Running 
 Container keila-0.30.3-web Running 
 Container keila-0.30.3-postgres Running 

升级版本:把编排文件中的 pentacent/keila:0.30.3 换成新 tag 后执行 docker compose up -d,Keila 启动时会自动跑增量迁移。先备份 pgdata 再升级(见下)。

数据备份:需要备份的是宿主机上的 ./pgdata/(业务数据)与 ./uploads/(用户内容)两个目录。数据库建议逻辑备份替代文件拷贝,避免备份时刻数据不一致:

docker exec keila-0.30.3-postgres pg_dump -U keila keila > keila_$(date +%F).sql

彻底卸载:

docker compose down            # 停止并删除容器与默认网络(external 网络保留)
rm -rf /opt/keila/pgdata /opt/keila/uploads   # 删除数据(先备份)
docker network rm keila-net    # 移除自定义网络

八、常见问题

8.1 容器反复重启,日志报 You must provide the DB_URL

DB_URL 未设置或拼写错误。Keila 的 runtime 配置在启动时强校验必填变量(DB_URL、SECRET_KEY_BASE、MAILER_SMTP_HOST、MAILER_SMTP_FROM_EMAIL),缺任何一项直接 System.halt(1) 退出,表现为容器反复重启。

8.2 启动报 random password 但不知道密码

首次启动时未设置 KEILA_USER/KEILA_PASSWORD,种子以 root@localhost + 随机密码创建了管理员(随机密码打印在启动日志的 warning 行)。清空 pgdata 目录重建即可让种子按新环境变量重新执行。

8.3 页面能打开但样式全丢

官方镜像内置了构建好的静态资源,正常不会出现该问题;若自行 build 过本地镜像,确认构建阶段执行了 mix assets.deploy。

8.4 登录后邮件里的链接指向 localhost

URL_HOST 未正确设置。Keila 用它生成邮件内链接与站点 URL,须写服务器对外可达的 IP:端口 或域名(含映射端口),不能只写容器内地址。

8.5 mailpit 健康检查失败 / 收不到测试邮件

mailpit 的 Web 界面端口是 8025(本文映射到宿主机 8138),SMTP 端口是 1025(仅容器网络内可用,未对外映射)。确认 MAILER_SMTP_HOST 写的是服务名 mailpit 且两者在同一网络。

8.6 SECRET_KEY_BASE 更换后所有会话失效

正常现象。该密钥参与会话签名,更换后旧 Cookie 全部失效,用户需要重新登录。它也派生 Hashid 盐(未显式设置 HASHID_SALT 时),中途更换会导致已有联系人的外部 ID 全部变化,务必首次部署就固化。

九、参考链接