Apache Pony Mail 0.11-incubating Docker Compose 部署指南

Apache Pony Mail 0.11-incubating Docker Compose 部署指南:自建镜像打包 ES 2.4.6、JDK8 与 lmtpd,ES 堆 256MB,Dockerfile 与实测回显

Apache Pony MailApache Pony Mail2026/10/13

Apache Pony Mail 0.11-incubating Docker Compose 部署指南

目标:使用 Docker Compose 编排 Apache Pony Mail 0.11-incubating 全套运行栈——Elasticsearch 2.4.6(JDK8)+ Apache(mod_lua/Lua 5.3)+ Pony Mail 0.11 + LMTP 归档网关 lmtpd,通过自建镜像实现「一条 docker compose 命令拉起整套邮件归档浏览系统」。端口映射到宿主机,ES JVM 堆限定 256MB,全程命令与回显均为实际执行结果。

一、环境信息

  • Docker:24.x(docker compose v2 子命令)
  • Pony Mail 镜像:自建 ponymail:0.11(基于 ubuntu:22.04,1.23GB)
  • 服务名:ponymail;容器名:ponymail-server
  • 映射端口:18080:8080(Web UI,宿主 8080 已被 haproxy 占用故用 18080)、8024:8024(LMTP 归档入口)、9200:9200(ES REST)
  • 数据卷:./data → /opt/elasticsearch/data(ES 数据持久化)
  • 镜像内含版本:ES 2.4.6、JDK8、Apache 2.4.52、Pony Mail 0.11-incubating

⚠️ 受限前置说明:Pony Mail 0.11-incubating 是 2019-03 发布的最后版本,项目已休眠,官方未提供任何 Docker 镜像,需自建。其代码面向 Elasticsearch 1.x/2.x 时代,必须搭配 ES 2.x 引擎与 python-elasticsearch 2.x 客户端(archiver.py 源码校验 ES_MAJOR == 2,客户端 1.9.0 会直接报错),现代 ES(7.x/8.x)无法兼容。本指南镜像即按此版本组合构建并实测。

二、镜像选型说明

镜像说明
ponymail:0.11(自建)本指南采用,基于 ubuntu:22.04,单镜像内打包 ES 2.4.6 + JDK8 + Apache(mod_lua)+ Pony Mail 0.11 + lmtpd 网关,开箱即用
Docker Hub 官方镜像不存在。Apache Pony Mail 从未发布过官方容器镜像,第三方镜像均不可考,不建议使用

镜像内容固定为「Pony Mail 历史版本兼容栈」:ES 2.4.6 只支持 Java 8(故内置 JDK8,JAVA_HOME=/opt/jdk8);mod_lua 基于 Lua 5.3,而 Ubuntu 22.04 的 lua-cjson 包只带 5.1/5.2 模块,镜像内对 mpx/lua-cjson 2.1.0 源码编译出 5.3 版 cjson.so。这些版本锁定关系是 Pony Mail 0.11 能否跑起来的关键。

三、前置条件

  • 装有 Docker 与 Docker Compose v2 的服务器,root 权限。
  • 能访问 Docker Hub(拉取 ubuntu:22.04 基础镜像);本指南实测环境构建机无法从容器内直连 github.com,因此在宿主机预下载全部制品到 vendor/ 后以 COPY 方式进镜像(详见 4.2 说明)。
  • 宿主机需能访问:Apache 镜像站(Pony Mail tar.gz)、Adoptium(JDK8)、download.elastic.co(ES 2.4.6)、PyPI(python 依赖)。

四、部署步骤

4.1 创建目录结构

mkdir -p ponymail-compose/conf ponymail-compose/data ponymail-compose/vendor/pip
cd ponymail-compose

vendor/ 用于存放预下载制品,目录结构:

文件大小来源
vendor/cjson.tar.gz71,947 字节https://github.com/mpx/lua-cjson/archive/refs/tags/2.1.0.tar.gz
vendor/jdk8.tar.gz103,542,511 字节Adoptium API(Temurin 8 GA,linux x64)
vendor/es.tar.gz27,350,245 字节https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.4.6/elasticsearch-2.4.6.tar.gz
vendor/ponymail.tar.gz1,055,853 字节https://downloads.apache.org/ponymail/ponymail/apache-pony-mail-0.11-incubating.tar.gz
vendor/pip/*.whl~4.8MB 合计pip download elasticsearch==2.4.0 formatflowed netaddr certifi chardet
# 宿主机一次性预下载(示例,实测全部成功)
curl -fsSL -o vendor/cjson.tar.gz https://github.com/mpx/lua-cjson/archive/refs/tags/2.1.0.tar.gz
curl -fsSL -o vendor/jdk8.tar.gz 'https://api.adoptium.net/v3/binary/latest/8/ga/linux/x64/jdk/hotspot/normal/eclipse'
curl -fsSL -o vendor/es.tar.gz https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.4.6/elasticsearch-2.4.6.tar.gz
curl -fsSL -o vendor/ponymail.tar.gz https://downloads.apache.org/ponymail/ponymail/apache-pony-mail-0.11-incubating.tar.gz
python3 -m pip download -d vendor/pip 'elasticsearch==2.4.0' formatflowed netaddr certifi chardet

4.2 编写自建镜像的 Dockerfile

cat > Dockerfile <<'EOF'
# Apache Pony Mail 0.11-incubating 自建镜像
# 单镜像内含:ES 2.4.6 + JDK8 + Apache(mod_lua/Lua5.3) + Pony Mail 0.11 + lmtpd 网关
# 构建说明:所有外网制品已预下载到 vendor/(宿主机下载),构建过程零外网依赖
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive

# 1) 基础包:Apache + Lua 5.3 依赖 + 构建工具
RUN apt-get update && apt-get install -y --no-install-recommends \
        apache2 python3 python3-pip wget curl ca-certificates \
        gcc make liblua5.3-dev lua-socket lua-sec net-tools procps \
    && rm -rf /var/lib/apt/lists/*

# 2) Lua 5.3 cjson(Ubuntu 22.04 的 lua-cjson 仅含 5.1/5.2,mod_lua 基于 Lua 5.3,需源码编译)
COPY vendor/cjson.tar.gz /tmp/cjson.tar.gz
RUN cd /tmp \
    && tar -xzf cjson.tar.gz && cd lua-cjson-2.1.0 \
    && make LUA_INCLUDE_DIR=/usr/include/lua5.3 \
    && cp cjson.so /usr/lib/x86_64-linux-gnu/lua/5.3/ \
    && rm -rf /tmp/lua-cjson-2.1.0 /tmp/cjson.tar.gz

# 3) JDK 8(ES 2.4.6 仅支持 Java 8)
COPY vendor/jdk8.tar.gz /opt/jdk8.tar.gz
RUN mkdir -p /opt/jdk8 \
    && tar -xzf /opt/jdk8.tar.gz -C /opt/jdk8 --strip-components=1 \
    && rm -f /opt/jdk8.tar.gz

# 4) Elasticsearch 2.4.6(ES 2.x 拒绝 root 运行,建 esuser)
COPY vendor/es.tar.gz /opt/es.tar.gz
RUN useradd -m -s /bin/bash esuser \
    && cd /opt \
    && tar -xzf es.tar.gz && mv elasticsearch-2.4.6 /opt/elasticsearch && rm -f es.tar.gz \
    && sed -i 's/^# cluster.name: my-application/cluster.name: ponymail/; s/^# node.name: node-1/node.name: ponymail-node-1/; s/^# network.host: 192.168.0.1/network.host: 0.0.0.0/; s/^# http.port: 9200/http.port: 9200/' /opt/elasticsearch/config/elasticsearch.yml \
    && chown -R esuser:esuser /opt/elasticsearch

# 5) Pony Mail 0.11-incubating + Python 依赖(elasticsearch 2.x 库,archiver 校验 ES_MAJOR==2)
COPY vendor/ponymail.tar.gz /opt/ponymail.tar.gz
COPY vendor/pip/ /tmp/pip/
RUN pip3 install --no-cache-dir --no-index --find-links=/tmp/pip/ \
        'elasticsearch==2.4.0' formatflowed netaddr certifi chardet \
    && rm -rf /tmp/pip \
    && mkdir -p /opt/ponymail \
    && tar -xzf /opt/ponymail.tar.gz -C /opt/ponymail && rm -f /opt/ponymail.tar.gz

# 6) 构建期初始化:起 ES(临时,仅本 RUN 步骤存活)-> 运行 setup.py 建索引与配置
RUN su esuser -c 'JAVA_HOME=/opt/jdk8 ES_HEAP_SIZE=256m /opt/elasticsearch/bin/elasticsearch -d' \
    && sleep 20 \
    && curl -s http://127.0.0.1:9200/ \
    && cd /opt/ponymail/tools && python3 setup.py --defaults --clobber

# 7) Apache vhost(8080)+ mod_lua
RUN a2enmod lua \
    && printf 'Listen 8080\n<VirtualHost *:8080>\n    ServerName ponymail.localhost\n    DocumentRoot /opt/ponymail/site\n    LuaPackageCPath /usr/lib/x86_64-linux-gnu/lua/5.3/?.so\n    LuaPackagePath  /usr/share/lua/5.3/?.lua\n    AddHandler      lua-script .lua\n    LuaScope        thread\n    LuaCodeCache    stat\n    AcceptPathInfo  On\n    <Directory /opt/ponymail/site>\n        Require all granted\n    </Directory>\n</VirtualHost>\n' > /etc/apache2/sites-available/ponymail.conf \
    && a2ensite ponymail

COPY ponymail-lmtpd.py /opt/ponymail-lmtpd.py
COPY entrypoint.sh /opt/entrypoint.sh
RUN chmod +x /opt/entrypoint.sh

EXPOSE 8080 8024 9200
ENTRYPOINT ["/opt/entrypoint.sh"]
EOF

⚠️ 构建期说明:Dockerfile 第 6 步会在构建时临时拉起 ES 并执行 setup.py --defaults --clobber,把 ponymail.cfg、site/api/lib/config.lua、site/js/config.js 一并生成到镜像里,保证容器首启即可用。若 buildkit 构建网络无法直连 github.com(本指南实测 curl: (7) Failed to connect to github.com port 443: Connection refused),不要试图在 RUN 里下载,一律采用宿主机预下载 + COPY 方案。

4.3 编写 lmtpd 网关与入口脚本

LMTP 归档网关(RFC 2033 子集,监听 8024,把邮件喂给 archiver.py 归档到 ES):

cat > ponymail-lmtpd.py <<'EOF'
#!/usr/bin/env python3
import socket, subprocess, threading, tempfile, os

HOST, PORT = "0.0.0.0", 8024

def handle(conn):
    def resp(code, msg=b"OK"): conn.sendall(b"%d %s\r\n" % (code, msg))
    f = conn.makefile("rb")
    resp(220, b"ponymail-lmtpd ESMTP")
    rcpt = None
    while True:
        line = f.readline()
        if not line: break
        line = line.rstrip(b"\r\n").decode("utf-8", "ignore")
        if line.upper().startswith("HELO") or line.upper().startswith("EHLO"):
            resp(250)
        elif line.upper().startswith("MAIL FROM"):
            resp(250)
        elif line.upper().startswith("RCPT TO"):
            rcpt = line[8:]
            resp(250)
        elif line.upper() == "DATA":
            resp(354, b"End data with <CR><LF>.<CR><LF>")
            data = b""
            while True:
                l = f.readline()
                if l == b".\r\n": break
                if l.startswith(b".."): l = l[1:]
                data += l
            fd, path = tempfile.mkstemp(prefix="msg-", suffix=".eml")
            with os.fdopen(fd, "wb") as fp: fp.write(data)
            proc = subprocess.Popen(
                ["python3", "/opt/ponymail/tools/archiver.py", path],
                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            out, _ = proc.communicate()
            os.unlink(path)
            resp(250 if proc.returncode == 0 else 451, b"archived")
            print("archived rc=%d rcpt=%s bytes=%d" % (proc.returncode, rcpt, len(data)))
        elif line.upper() == "RSET":
            resp(250)
        elif line.upper() == "QUIT":
            resp(221); break
        else:
            resp(250)
    conn.close()

srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT)); srv.listen(16)
print("ponymail-lmtpd listening on %s:%d" % (HOST, PORT), flush=True)
while True:
    c, _ = srv.accept()
    threading.Thread(target=handle, args=(c,), daemon=True).start()
EOF

容器入口脚本(chown 数据卷 → 以 esuser 起 ES → 确保索引 → 起 lmtpd → 前台跑 Apache):

cat > entrypoint.sh <<'EOF'
#!/bin/bash
# Pony Mail 容器入口:启动 ES -> 确保索引 -> 启动 lmtpd 网关 -> 前台运行 Apache
chown -R esuser:esuser /opt/elasticsearch/data 2>/dev/null

su esuser -c 'JAVA_HOME=/opt/jdk8 ES_HEAP_SIZE=${ES_HEAP_SIZE:-256m} /opt/elasticsearch/bin/elasticsearch -d -p /tmp/es.pid'
sleep 15
curl -s http://127.0.0.1:9200/ >/dev/null || { echo "ES failed to start"; exit 1; }

# 索引缺失时(如首次挂载空数据卷)用 setup.py 重建
cd /opt/ponymail/tools && python3 setup.py --defaults --skiponexist --clobber >/tmp/setup.log 2>&1

nohup python3 /opt/ponymail-lmtpd.py > /var/log/ponymail-lmtpd.log 2>&1 &

exec apache2ctl -DFOREGROUND
EOF
chmod +x entrypoint.sh ponymail-lmtpd.py

4.4 编写 docker-compose.yml

cat > docker-compose.yml <<'EOF'
services:
  ponymail:
    build:
      context: .
      network: host
    image: ponymail:0.11
    container_name: ponymail-server
    restart: unless-stopped
    ports:
      - "18080:8080"  # Pony Mail Web UI(Apache + mod_lua);宿主 8080 已被 haproxy 占用,故映射 18080
      - "8024:8024"   # LMTP 归档入口(lmtpd -> archiver.py)
      - "9200:9200"   # Elasticsearch 2.4.6 REST(排障/核对用)
    environment:
      ES_HEAP_SIZE: "256m"   # ES JVM 堆(-Xms256m -Xmx256m)
    volumes:
      - ./data:/opt/elasticsearch/data   # ES 数据持久化
EOF
配置项说明
build.context: . / image: ponymail:0.11自建镜像,构建上下文为当前目录(含 Dockerfile 与 vendor/)
ports: "18080:8080"Web UI 映射;宿主 8080 已被 haproxy 占用,按需改为 "8080:8080"
ports: "8024:8024"LMTP 归档入口(邮件客户端投递到此端口)
ports: "9200:9200"ES REST,排障/核对用
environment: ES_HEAP_SIZE=256m同时设定 ES 的 -Xms/-Xmx(实测生效 -Xms256m -Xmx256m)
volumes: ./data:/opt/elasticsearch/dataES 索引数据持久化,docker compose down 不丢数据
restart: unless-stopped异常退出自动拉起

4.5 构建并启动服务

docker compose up -d --build

实测回显(节选):

[+] Building 1.7s (23/23) FINISHED
Image ponymail:0.11 Built
Container ponymail-server  Created
Container ponymail-server  Starting
Container ponymail-server  Started

说明:本机已先构建过镜像,故此处 --build 命中缓存秒级完成;首次构建约需 3-5 分钟(apt 安装 + cjson 编译 + 制品解压 + 构建期 setup.py 初始化)。

4.6 验证服务

docker compose ps

实测回显:

NAME              IMAGE           COMMAND                SERVICE    CREATED          STATUS                  PORTS
ponymail-server   ponymail:0.11   "/opt/entrypoint.sh"   ponymail   35 seconds ago   Up 35 seconds   0.0.0.0:8024->8024/tcp, [::]:8024->8024/tcp, 0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp, 0.0.0.0:18080->8080/tcp, [::]:18080->8080/tcp

ES 健康检查(应返回 2.4.6 版本信息):

curl -s http://127.0.0.1:9200/

实测回显(节选):

{
  "name" : "ponymail-node-1",
  "cluster_name" : "ponymail",
  "cluster_uuid" : "nk6gYul1SmWoQOlq8iowXg",
  "version" : {
    "number" : "2.4.6",
    "build_hash" : "5376dca9f70f3abef96a77f4bb22720ace8240fd",
    "build_timestamp" : "2017-07-18T12:17:44Z",
    "build_snapshot" : false,
    "lucene_version" : "5.5.4"
  },
  "tagline" : "You Know, for Search"
}

Web API 检查(应返回 JSON):

curl -s http://127.0.0.1:18080/api/pminfo.lua

实测回显:

{"hits":1,"activity":[[1791763200000,1]],"participants":1,"no_active_lists":1,"no_threads":1}

Web 首页检查(应返回 HTML 页面):

curl -s http://127.0.0.1:18080/ | head -5

实测回显:

<!DOCTYPE html>
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership. The

LMTP 归档链路验证(发送测试邮件 → 核对 ES 计数):

# 发送脚本(宿主机执行,需 python3 与 smtplib)
python3 send-lmtp-test.py

实测回显:

LMTP send OK
curl -s http://127.0.0.1:9200/ponymail/mbox/_count

实测回显:

{"count":1,"_shards":{"total":1,"successful":1,"failed":0}}

搜索验证(应命中 alice 的邮件):

curl -s 'http://127.0.0.1:9200/ponymail/mbox/_search?q=from:alice&pretty' | head -20

实测回显(节选):

{
  "took" : 16,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.625,
    "hits" : [ {
      "_index" : "ponymail",
      "_type" : "mbox",
      "_id" : "re040e3adc798ee1db640fe1c080917dffb7995d7f75f54b7577fe1bb@<ponymail-test.example.org>",
      "_source" : {
        "from_raw" : "alice@example.org",
        "from" : "alice@example.org",
        "to" : "ponymail-test@example.org",
        "subject" : "Pony Mail LMTP test",
        "message-id" : "<lmtp-test-001@example.org>",
        "cc" : "",
        "epoch" : 1791770400,
        "list" : "<ponymail-test.example.org>",
        "list_raw" : "<ponymail-test.example.org>",
        "date" : "2026/10/12 02:00:00",
        "private" : false,
        "references" : "",

重复投递去重验证(同一 Message-ID 再发一封,count 不增):

python3 send-lmtp-test.py && sleep 3 && curl -s http://127.0.0.1:9200/ponymail/mbox/_count

实测回显:

LMTP send OK
{"count":1,"_shards":{"total":1,"successful":1,"failed":0}}

JVM 堆生效核对(容器内应见 -Xms256m -Xmx256m):

docker exec ponymail-server bash -lc 'ps aux | grep [e]lasticsearch' | grep -oE '\-Xm[sx][0-9a-z]+' | sort -u

实测回显:

-Xms256m
-Xmx256m

五、数据持久化说明

  • ./data 挂载到 /opt/elasticsearch/data,ES 索引数据(含已归档邮件)全部落在宿主机该目录,docker compose down 只删除容器不删除数据。
  • 实测:归档 1 封邮件后执行 docker compose down && docker compose up -d,重启后 _count 仍为 1,数据完整保留:
cd ponymail-compose && docker compose down && docker compose up -d && sleep 25 \
  && curl -s http://127.0.0.1:9200/ponymail/mbox/_count

实测回显:

Container ponymail-server  Removed
Network ponymail-compose-lab_default  Removing
Network ponymail-compose-lab_default  Removed
Container ponymail-server  Created
Container ponymail-server  Starting
Container ponymail-server  Started
{"count":1,"_shards":{"total":1,"successful":1,"failed":0}}
  • 首次挂载空数据卷时,entrypoint.sh 会自动执行 setup.py --defaults --skiponexist --clobber 重建索引与配置,无需手工干预。

六、常用环境变量表

环境变量说明本指南值
ES_HEAP_SIZEES JVM 堆,同时设定 -Xms/-Xmx256m
JAVA_HOME(entrypoint 内固定)ES 2.4.6 需 Java 8/opt/jdk8

内存说明:ES 2.4.6 单节点归档场景堆无需过大,256m 即可稳定运行(实测 ps 可见 -Xms256m -Xmx256m)。若单节点归档邮件量极大,可上调至 512m,但不要超过宿主机物理内存的一半(ES 官方建议)。

七、常用配置参数说明(command)

镜像 ENTRYPOINT 固定为 /opt/entrypoint.sh,容器 PID 1 为该脚本,最终 exec apache2ctl -DFOREGROUND 前台接管 Apache:

配置方式说明
ENTRYPOINT ["/opt/entrypoint.sh"]镜像内固定;chown 数据卷 → 起 ES → 确保索引 → 起 lmtpd → 前台跑 Apache
command(compose 覆盖)不建议覆盖 ENTRYPOINT;如需临时排障可 docker exec 进容器操作
docker exec ponymail-server bash -lc 'tail -20 /var/log/ponymail-lmtpd.log'进入容器执行运维命令(如核对 ES 进程、查日志)

与官方发行版部署的区别:Compose 方案把「systemd 托管的 ES/lmtpd」改为「entrypoint 脚本按序拉起」,这是容器内无 systemd 环境的标准做法;归档链路(LMTP → archiver.py → ES)与官方部署完全一致。

八、配置文件持久化(conf 挂载)

Pony Mail 的关键配置在容器内 /opt/ponymail/tools/ponymail.cfg(归档器/导入器)与 /opt/ponymail/site/api/lib/config.lua(mod_lua 后端)。如需持久化自定义配置,在 compose 中追加挂载并配合 --skiponexist 语义:

    volumes:
      - ./data:/opt/elasticsearch/data
      - ./conf:/opt/ponymail/conf   # 可选:持久化自定义配置

注意:setup.py 会把生成文件写到 tools/ponymail.cfg 与 site/api/lib/config.lua。若将 conf 目录挂到 /opt/ponymail/conf,只适用于「自行改 setup.py 输出路径」的高级场景;常规用法仍是进入容器修改这两个文件后 docker compose restart ponymail。本指南镜像构建期已生成默认配置,开箱即用。

九、等价 docker run 运行方式

不写 docker-compose.yml 时,也可用单条 docker run(等价于 4.4 的 compose 配置):

docker run -d --name ponymail-server --restart unless-stopped \
  -p 18080:8080 -p 8024:8024 -p 9200:9200 \
  -e ES_HEAP_SIZE=256m \
  -v "$PWD/data:/opt/elasticsearch/data" \
  ponymail:0.11

十、常用运维命令

docker compose up -d            # 启动
docker compose up -d --build    # 重新构建并启动(改 Dockerfile/vendor 后)
docker compose ps               # 查看状态
docker compose restart ponymail # 重启服务
docker compose down             # 停止并删除容器(保留 ./data 数据卷)
docker compose logs -f ponymail # 查看容器日志(Apache 访问/错误、lmtpd 归档记录)
docker exec ponymail-server bash -lc 'tail -20 /var/log/ponymail-lmtpd.log'   # 查 lmtpd 归档日志
docker exec ponymail-server bash -lc 'tail -20 /tmp/setup.log'                 # 查 setup.py 日志

十一、常见问题(FAQ)

11.1 构建报 curl: (7) Failed to connect to github.com port 443

原因:buildkit 构建网络无法直连 github.com(本指南实测 Connection refused)。 解决:不要在 RUN 步骤里 curl 外网;改为宿主机预下载制品到 vendor/ 后用 COPY 进镜像。Dockerfile 已按此方案编写。

11.2 docker compose up 报 failed to bind host port 0.0.0.0:8080/tcp: address already in use

原因:宿主 8080 已被其他进程占用(本指南实测宿主机 haproxy 占用 8080)。 解决:改用空闲端口映射,如 "18080:8080";或先停掉占用进程。

11.3 Web 打开 403 Forbidden

原因:DocumentRoot 在 /var/www 之外,Apache 默认目录授权不覆盖。 解决:vhost 内必须添加 <Directory /opt/ponymail/site> Require all granted </Directory>(Dockerfile 第 7 步已包含)。

11.4 Web 500 报 module 'cjson' not found

原因:mod_lua 基于 Lua 5.3,而 Ubuntu 的 lua-cjson 包只带 5.1/5.2 模块。 解决:对 mpx/lua-cjson 2.1.0 源码编译(make LUA_INCLUDE_DIR=/usr/include/lua5.3),把 cjson.so 放到 /usr/lib/x86_64-linux-gnu/lua/5.3/(Dockerfile 第 2 步已包含)。

11.5 archiver 报 Exception: ('Unexpected elasticsearch version ', (1, 9, 0))

原因:python-elasticsearch 版本不对——archiver.py 源码校验 ES_MAJOR == 2,客户端 1.9.0 会报错。 解决:安装 elasticsearch==2.4.0(Dockerfile 第 5 步已锁定),并确认 ES 引擎为 2.4.6。

11.6 ES 起不来,日志报拒绝 root 运行

原因:ES 2.x 拒绝以 root 身份启动。 解决:以 esuser 运行(entrypoint.sh 中 su esuser -c 'JAVA_HOME=/opt/jdk8 ES_HEAP_SIZE=256m /opt/elasticsearch/bin/elasticsearch -d'),并 chown -R esuser:esuser /opt/elasticsearch。

11.7 LMTP 投递后 ES count 不增加

解决:确认 lmtpd 已监听(docker exec ponymail-server bash -lc 'ss -tlnp | grep 8024')、archiver.py 无报错(/var/log/ponymail-lmtpd.log);同一 Message-ID 重复投递属于去重行为,count 不会增加。

十二、参考链接