Signature PDF 1.11.0 Docker Compose 部署指南

在 Ubuntu 26.04 服务器上用 Docker Compose 从零部署 Signature PDF 1.11.0 官方镜像,完成端口规划、上传限额、共享存储持久化与协议层功能验证,含镜像拉取降级实录与等价 docker run。

Signature PDFSignature PDFPDFDockerDocker Compose2026/11/02

Signature PDF 1.11.0 Docker Compose 部署指南

目标:在一台 Ubuntu 服务器上用 Docker Compose 从零部署 Signature PDF 1.11.0(24eme/signaturepdf 官方镜像 xgaia/signaturepdf),完成端口规划、上传限额、共享存储持久化与协议层功能验证。已在 Ubuntu 26.04.1 LTS(x86_64,Docker 29.9.2 / Compose v5.6.1)实测通过。

一、环境信息

  • 操作系统:Ubuntu 26.04.1 LTS(x86_64,内核 7.0.0-38-generic)
  • Docker:Docker 29.9.2、Docker Compose v5.6.1(v2.x 语法兼容)
  • 应用版本:Signature PDF v1.11.0(官方镜像 xgaia/signaturepdf:1.11.0,镜像 ID e4e417983384,磁盘占用 1.37GB / 内容 344MB)
  • 应用栈:Apache 2.4.69(Debian)+ PHP 8.2.35,基础镜像 php:8.2-apache(来源:镜像 Dockerfile FROM php:8.2-apache,启动日志回显 Apache/2.4.69 (Debian) PHP/8.2.35 configured -- resuming normal operations)
  • 应用监听端口:8140(映射到应用内部 Apache 80 端口)
  • 数据目录:./data(bind mount 到应用内 /data,存放多人协作共享的 PDF)
  • 内网网段:10.89.60.0/24(独立 Compose 网络,网关 10.89.60.1,应用固定地址 10.89.60.10)
  • 上传限额:单文件 48M、POST 48M、单次最多 401 个文件(即 400 页 + 原 PDF)

💡 技术拓展:Signature PDF 官方 installation.md 提供四条安装路线,形态对比——

路线形态特点
Docker(官方镜像 xgaia/signaturepdf)单服务、零数据库本文采用;依赖全部打进镜像,升级换 tag 即可
Debian/Ubuntu 裸机git clone + php -S 或 Apache需自装 php/librsvg2-bin/pdftk/imagemagick/potrace/ghostscript/gpg 六件套
deb 包(make deb)系统包托管依赖由 apt 解析,适合纯 Debian 系批量装机
Alpine 脚本 / YunoHost社区安装脚本非官方镜像形态,组件版本自行把控

本文选 Docker 路线:该应用无数据库、无队列,一个服务即可跑通全部功能,是容器化成本最低的一类 Web 应用。裸机与 deb 路线的依赖清单与 PHP/Apache 配置见第九章等价 docker run 对照及官方 installation.md。

二、镜像选型说明

Signature PDF 仓库本身不含官方 Compose 模板,官方推荐的容器入口是 installation.md 中的一条命令:

docker run -d --name=signaturepdf -p 8080:80 xgaia/signaturepdf

xgaia/signaturepdf 是项目维护方在 Docker Hub 发布的官方镜像(README 徽章直链),版本 tag 与 GitHub tag 同步(1.11.0、1.10.0 等)。本文固定使用 1.11.0 而非 latest,保证升级可控。镜像内配置机制(《Signature PDF 1.11.0 配置参数详解》 有逐项实测):启动时 entrypoint.sh 用 envsubst 把三个模板(apache.conf、php.ini、config.ini.tpl)渲染为生效配置,因此所有部署参数都通过环境变量注入,无需挂载配置文件。

⚠️ 网络情况说明(2026-11-02 实测):本机拉取官方镜像时按「官方源 → 国内源 → 代理」降级链执行,过程如下——

  1. docker pull xgaia/signaturepdf:latest(Docker Hub 官方源)——长时间挂起,超过 40 分钟无进展,终止;
  2. docker pull docker.m.daocloud.io/xgaia/signaturepdf——DaoCloud 加速器返回「这镜像不在白名单」(public-image-mirror 白名单机制未收录该仓库),失败;
  3. docker pull docker.1ms.run/xgaia/signaturepdf:1.11.0——成功,实测回显:
1.11.0: Pulling from xgaia/signaturepdf
Digest: sha256:e4e4179833848bd874b99a55720718a08bf857552a30623e734c92af41aeded2
Status: Downloaded newer image for docker.1ms.run/xgaia/signaturepdf:1.11.0

拉取后将 tag 规范回官方名(docker tag docker.1ms.run/xgaia/signaturepdf:1.11.0 xgaia/signaturepdf:1.11.0),digest 与 Docker Hub 官方一致(e4e417983384)。另备有 docker save 产物 xgaia-signaturepdf-1.11.0.tar(343,920,640 字节),离线环境可 docker load -i 直接导入。本机代理实况:192.168.1.72:7897 可用(curl -x 测试 registry-1.docker.io 返回 401=连通),192.168.1.62:7897 已失效(连接返回 000);若你的环境可直连 Docker Hub,第 1 步即可成功,无需降级。

三、前置条件

部署前确认服务器满足以下条件,逐项给出验证命令与实测回显:

  1. Docker 与 Compose 插件可用:
docker --version && docker compose version

实测回显:

Docker version 29.9.2, build a82e528
Docker Compose version v5.6.1
  1. 目标端口未被占用。本文选用 8140 端口,确认方式:
ss -ltn | grep 8140 || echo "port 8140 free"

实测回显(空闲时):

port 8140 free
  1. 规划 Compose 专用网段。为避免与服务器上已有网络冲突,先查看现有网段占用:
docker network ls -q | xargs docker network inspect --format '{{.Name}} -> {{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null | grep '10\.89\.'

实测回显(节选,按你服务器实际输出为准):

papermerge-353-stack_papermerge-net -> 10.89.57.0/24
plainpad-129-net -> 10.89.58.0/24
marmot-020-stack_marmot-net -> 10.89.56.0/24

⚠️ 注意:网段选择必须避开已有网络(包括已停止服务遗留的网络),冲突时创建会报 invalid pool request: Pool overlaps with other one on this address space。本文实测 10.89.60.0/24 空闲,选用之。创建前务必以上方命令的全量输出为准。

  1. 创建部署目录与共享存储目录:
mkdir -p /opt/signaturepdf/data

无输出即成功,用 ls -d /opt/signaturepdf/* 确认目录已创建。data 目录属主无需手工调整——应用启动时入口脚本会自动 mkdir -p 并 chown www-data(见第五章)。

四、部署步骤

4.1 编写 Compose 文件

在 /opt/signaturepdf/ 下创建 docker-compose.yml,内容如下(与本文实测文件一致):

name: signaturepdf-1.11.0-stack
services:
  signaturepdf:
    image: xgaia/signaturepdf:1.11.0
    container_name: signaturepdf-1.11.0-web
    restart: unless-stopped
    ports:
      - "8140:80"
    environment:
      SERVERNAME: "localhost:8140"
      UPLOAD_MAX_FILESIZE: "48M"
      POST_MAX_SIZE: "48M"
      MAX_FILE_UPLOADS: "401"
      PDF_STORAGE_PATH: "/data"
      DEFAULT_LANGUAGE: "en_US.UTF-8"
    volumes:
      - ./data:/data
    networks:
      spnet:
        ipv4_address: 10.89.60.10

networks:
  spnet:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.60.0/24
          gateway: 10.89.60.1

逐项说明:

  • name: signaturepdf-1.11.0-stack:显式指定 Compose 项目名。注意 Compose 会将项目名中的点号规范化为短横线,实际生成的网络名为 signaturepdf-1110-stack_spnet,后续跨目录清理需用规范化后的名称(见第十章)。
  • image: xgaia/signaturepdf:1.11.0:固定版本号而非 latest,与 GitHub v1.11.0 tag 对应。
  • container_name: signaturepdf-1.11.0-web:固定命名便于 docker logs/docker exec 定位;单副本部署专用,多副本时删掉此行。
  • "8140:80":应用内部为 Apache 固定监听的 80 端口,宿主侧映射为 8140。
  • SERVERNAME: "localhost:8140":渲染进 Apache 虚拟主机 ServerName。生产环境填实际域名(如 pdf.example.org),否则 Apache 启动日志会出现 AH00558: Could not reliably determine the server's fully qualified domain name 告警(本文实测日志原样出现该行,功能不受影响)。
  • UPLOAD_MAX_FILESIZE / POST_MAX_SIZE / MAX_FILE_UPLOADS:三个 PHP 限额,官方默认 24M/24M/201,本文放宽到 48M/48M/401(详见第六章)。
  • PDF_STORAGE_PATH: "/data":多人协作共享的 PDF 存储目录;留空则整个共享模式关闭(签名页隐藏「分享」入口)。
  • DEFAULT_LANGUAGE: "en_US.UTF-8":官方默认 fr_FR.UTF-8;该值决定启动时 locale-gen 生成哪个系统 locale,进而决定界面默认语言(见第十一章 Q4)。
  • ./data:/data:共享存储 bind mount 到宿主目录,卸载重建不丢数据(第五章)。
  • ipv4_address: 10.89.60.10:固定内网地址,便于前置反代与健康检查写死目标。

4.2 启动服务栈

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

实测回显:

 Network signaturepdf-1110-stack_spnet Creating 
 Network signaturepdf-1110-stack_spnet Created 
 Container signaturepdf-1.11.0-web Creating 
 Container signaturepdf-1.11.0-web Created 
 Container signaturepdf-1.11.0-web Starting 
 Container signaturepdf-1.11.0-web Started

确认运行状态:

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

实测回显:

NAME                      STATUS         PORTS
signaturepdf-1.11.0-web   Up 5 seconds   0.0.0.0:8140->80/tcp, [::]:8140->80/tcp

4.3 验证 Web 服务就绪

curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8140/signature

实测回显:

200

首页(四大功能 Tab)同样返回 200,响应头可见服务端指纹:

HTTP/1.1 200 OK
Server: Apache/2.4.69 (Debian)
X-Powered-By: Fat-Free Framework
Set-Cookie: PHPSESSID=81f9177d9e31336ac5d109828bc74545; path=/; HttpOnly; SameSite=Lax

首次启动的日志可确认入口脚本完成了 locale 生成与 Apache 拉起:

docker compose logs --tail 5

实测回显(节选):

Generating locales (this might take a while)...
  en_US.UTF-8... done
Generation complete.
[Mon Nov 02 05:02:58.187798 2026] [mpm_prefork:notice] [pid 18:tid 18] AH00163: Apache/2.4.69 (Debian) PHP/8.2.35 configured -- resuming normal operations

💡 说明:Signature PDF 无数据库、无初始化步骤,up 完成即可用。浏览器打开 http://<服务器IP>:8140/,上传一份 PDF、画一个签名、点下载即完成最小闭环验证;多人协作共享与 API 的协议层实测见第十一章与 《Signature PDF 1.11.0 API 使用详解》。

五、数据持久化说明

本栈唯一的持久化数据是多人协作共享目录,通过 bind mount 落到宿主机:

数据应用内路径宿主机路径内容
共享 PDF/data/opt/signaturepdf/data每个共享一个 32/20 位哈希目录 + 两个旁路文件
配置/usr/local/signaturepdf/config/config.ini未挂载启动时由环境变量渲染生成,重建即复原(见第八章)

实测共享目录内部结构(一次「发起共享 + 追加签名」后):

data/
├── fjnvuogj6a8s89kpq46p/                  # 共享目录,目录名即分享链接中的 hash
│   ├── original.pdf                       # 发起人上传的原始 PDF(1061 字节)
│   ├── filename.txt                       # 原始文件名(供下载时还原命名)
│   ├── 20261102045840137443.svg.pdf       # 第 1 层签名(SVG 转成的 PDF 图层)
│   ├── 20261102045840137443.sign.pdf      # 第 1 层压印后的累积结果
│   ├── 20261102045857545781.svg.pdf       # 第 2 层签名
│   ├── 20261102045857545781.sign.pdf      # 第 2 层压印后的累积结果
│   └── final.pdf                          # 编译产物:当前全部层合成结果
├── fjnvuogj6a8s89kpq46p.admin             # 20 位管理密钥(删除共享用)
└── fjnvuogj6a8s89kpq46p.expire            # 0 字节文件,mtime 即过期时刻

三点务必知晓:

  1. 单人签名不落盘:不点「分享」的签名走流式处理,处理完即回传、服务器不留文件(README 原文承诺),/data 中不会出现任何目录;只有共享模式才写盘。
  2. .expire 的 mtime 就是保留期限:发起共享选「+1 hour」,则 stat 该文件看到 mtime = 创建时刻 + 1 小时;每次追加签名都会按保留期重新续期(实测两人先后签署后 mtime 刷新为第二人签署时刻 + 1 小时)。过期清理由 GET /cron 触发(第十章给出定时任务写法)。
  3. 加密共享时全部文件带 .gpg 后缀:启用 PDF_STORAGE_ENCRYPTION=true 后,目录内所有文件为 AES256 对称加密(original.pdf.gpg、final.pdf.gpg 等),密钥只存在于发起人浏览器 Cookie 与分享链接 # 片段中,服务器端不可读明文(实测见 《Signature PDF 1.11.0 配置参数详解》)。

备份只需打包宿主机 data/ 目录;恢复同理解包回原路径并 chown 给 Web 运行用户。

六、环境变量表

官方 installation.md「Deploy with Docker → Configuration」节给出的部署变量,与本文实测取值对照:

变量作用官方默认本部署取值
SERVERNAMEApache ServerName(部署 URL)localhostlocalhost:8140
UPLOAD_MAX_FILESIZE单个上传文件大小上限24M48M
POST_MAX_SIZE整个 POST 请求体上限24M48M
MAX_FILE_UPLOADS单次请求文件数上限 = 页数 + 1201(200 页)401(400 页)
PDF_STORAGE_PATH共享 PDF 存储目录;留空关闭共享模式/data/data
DISABLE_ORGANIZATION关闭页面整理 Tabfalse未设置(false)
PDF_DEMO_LINK演示 PDF 链接:true 显示 / false 隐藏 / 自定义 URLtrue未设置(显示默认示例)
DEFAULT_LANGUAGE界面默认语言(同时决定 locale-gen 目标)fr_FR.UTF-8en_US.UTF-8
PDF_STORAGE_ENCRYPTION共享存储 GPG 对称加密开关false未设置(false)

限额的实际生效值可从页面内嵌 JS 变量直接读到(无需进服务器查 php.ini):

curl -s http://localhost:8140/signature | grep -oE 'maxSize = [0-9]+|maxPage = [0-9]+'

实测回显:

maxSize = 48000000
maxPage = 400

解读:maxSize 取 POST_MAX_SIZE 与 UPLOAD_MAX_FILESIZE 的较小者(48M 按 1000 进制折算为 48000000 字节);maxPage = MAX_FILE_UPLOADS - 1。三个变量在签名页、整理页、压缩页同时生效,上传超限文件会被 PHP 在落盘前拒收。

💡 技术拓展:容量单位在 PHP 配置里按 1000 进制折算(M=×1000×1000),与操作系统的 1024 进制 MiB 不同——48M 实际是 48,000,000 字节 ≈ 45.8 MiB。设限额时若按「48 MiB」预期,应填 50M。本文实测 48M → 48000000 印证了这一折算规则。

七、配置参数说明

环境变量注入后并非直接生效,中间有一层「模板渲染」链(v1.11.0 源码 entrypoint.sh 实查):

docker compose up
  └─ entrypoint.sh
      ├─ envsubst < config/apache.conf      > /etc/apache2/sites-available/signaturepdf.conf   # ${SERVERNAME}
      ├─ envsubst < config/php.ini          > /usr/local/etc/php/conf.d/uploads.ini            # 三个限额变量
      ├─ envsubst < config/config.ini.tpl   > /usr/local/signaturepdf/config/config.ini        # 4 个应用键
      ├─ sed 取消注释 /etc/locale.gen 中的 $DEFAULT_LANGUAGE && locale-gen
      ├─ export LANG/LANGUAGE/LC_ALL=$DEFAULT_LANGUAGE
      ├─ mkdir -p $PDF_STORAGE_PATH && chown www-data:www-data
      └─ apache2-foreground

其中 config.ini.tpl 只渲染四个键(PDF_STORAGE_PATH、DISABLE_ORGANIZATION、PDF_DEMO_LINK、PDF_STORAGE_ENCRYPTION),实测生成的 config.ini 开头:

[globals]

; Path to which stored pdf to activate the mode of sharing a signature to several.
PDF_STORAGE_PATH=/data

; Disable organization tab and routes
DISABLE_ORGANIZATION=false

; Manage demo link pdf : true (by default, show), false (hide), or custom link
PDF_DEMO_LINK=

; Encryption activation (default activation if GPG is installed)
PDF_STORAGE_ENCRYPTION=false

应用其余配置项(管理页授权 IP、本地文件 API、元数据默认字段、自定义保留期、NSS 数字签名证书等)不在模板里,需要直接改 config.ini——逐项含义与实测行为见 《Signature PDF 1.11.0 配置参数详解》。

八、配置文件挂载

不要把 config.ini 直接 bind mount 进去:第七章的渲染链每次启动都会 envsubst 覆写该文件,挂载的文件内容会在启动瞬间被模板+环境变量重写的结果覆盖。实测佐证:向 config.ini 追加自定义段后 docker restart,重启后文件恢复为模板渲染的四个键。

需要持久化 config.ini 级配置(如 ADMIN_AUTHORIZED_IP、[signature] retention)时,两种正确做法:

  1. 挂载替换模板(推荐,改动最小):把定制过的 config.ini.tpl 挂到 /usr/local/signaturepdf/config/config.ini.tpl,启动时 envsubst 渲染的就是你的模板;
  2. 派生镜像:FROM xgaia/signaturepdf:1.11.0 + RUN sed -i '$a ...' /usr/local/signaturepdf/config/config.ini.tpl,把模板改动固化进镜像。

挂载示例(做法 1):

    volumes:
      - ./data:/data
      - ./config.ini.tpl:/usr/local/signaturepdf/config/config.ini.tpl:ro

九、等价 docker run

不使用 Compose 的等价单机跑法(与本文第四章等价,参数一一对应):

docker network create --subnet 10.89.60.0/24 --gateway 10.89.60.1 signaturepdf-net
docker run -d --name signaturepdf-1.11.0-web \
  -p 8140:80 \
  -e SERVERNAME="localhost:8140" \
  -e UPLOAD_MAX_FILESIZE=48M -e POST_MAX_SIZE=48M -e MAX_FILE_UPLOADS=401 \
  -e PDF_STORAGE_PATH=/data \
  -e DEFAULT_LANGUAGE=en_US.UTF-8 \
  -v /opt/signaturepdf/data:/data \
  --network signaturepdf-net --ip 10.89.60.10 \
  --restart unless-stopped \
  xgaia/signaturepdf:1.11.0

官方最小跑法(全默认值:24M 限额、200 页上限、法文界面、存储目录 /data):

docker run -d --name=signaturepdf -p 8080:80 xgaia/signaturepdf

裸机对照(Debian/Ubuntu,官方 installation.md 原文):apt-get install php librsvg2-bin pdftk imagemagick potrace ghostscript locales gpg 装齐依赖后 git clone 源码、php -S localhost:8000 -t public 即可起服务;PHP 侧手工设置 upload_max_filesize=24M、post_max_size=24M、max_file_uploads=201,Apache 侧配 FallbackResource /index.php。

十、常用运维命令

以下命令均在 /opt/signaturepdf/(compose 文件所在处)执行。

# 查看运行状态
docker compose ps
# 跟踪应用日志(Apache 前台日志,含 PHP 报错)
docker compose logs -f
# 重启应用(数据不动)
docker compose restart
# 停止并移除栈(bind mount 数据不受影响)
docker compose down
# 跨目录彻底清理时用规范化项目名(点号被转为短横线)
docker compose -p signaturepdf-1110-stack down

过期共享清理——应用没有内置定时器,/cron 路由需外部触发(实测 curl http://localhost:8140/cron 返回 200,过期目录被删除)。加一条系统 crontab 每小时清理:

(crontab -l 2>/dev/null; echo '0 * * * * curl -s http://localhost:8140/cron -o /dev/null') | crontab -

⚠️ 注意:实测发现 /cron 只删除过期共享目录与 .expire 文件,不清理旁路的 <hash>.admin 密钥文件(源码 MainController::cron 无该逻辑),长期运行会残留零星 20 字节小文件,可定期 find /opt/signaturepdf/data -maxdepth 1 -name '*.admin' ! -exec test -e {} \; -delete 级别的脚本或人工巡检处理(对照同名目录已不存在的才删)。

数据备份与恢复:

# 备份共享存储(含 .admin/.expire 旁路文件,恢复后管理密钥仍有效)
tar czf /opt/signaturepdf/data_backup_$(date +%F).tar.gz -C /opt/signaturepdf data
# 恢复:解回原位并交给 Web 运行用户
tar xzf /opt/signaturepdf/data_backup_2026-11-02.tar.gz -C /opt/signaturepdf
docker compose exec signaturepdf chown -R www-data:www-data /data

升级到新版本:改 compose 中 image: xgaia/signaturepdf:<新版本> 后 docker compose up -d 自动重建;回滚同理改回旧 tag。该应用无数据库迁移,版本切换零成本,但共享目录格式跨版本兼容(v1.5 以来布局未变),升级前后无需转换数据。

十一、常见问题

Q1:docker pull xgaia/signaturepdf 挂起或报「不在白名单」? 按第二章降级链处理:官方 Docker Hub 当时不可用(长时间挂起),DaoCloud 加速器白名单未收录该仓库;实测 docker.1ms.run/xgaia/signaturepdf:1.11.0 可拉取,拉完 docker tag 改回官方名即可。离线环境用 docker load -i xgaia-signaturepdf-1.11.0.tar。

Q2:发起共享返回 500 Internal Server Error? 实测复现:对同一个 hash 值重复 POST /share(例如浏览器回退后重复提交同一分享表单)返回 500,错误详情 mkdir(): File exists——共享目录已存在时 createShare 直接 mkdir 失败。hash 由前端随机生成(20 位小写字母数字),正常操作不会相撞;用 API 自建 hash 撞了换一个即可。

Q3:上传大 PDF 报「文件过大」或页面静默失败? 三重限额共同约束:单文件 ≤ UPLOAD_MAX_FILESIZE、请求体 ≤ POST_MAX_SIZE、文件数 ≤ MAX_FILE_UPLOADS - 1(页数上限)。整理多份 PDF 时请求体是「所有文件之和」,最容易先撞 POST_MAX_SIZE。改 compose 环境变量后 docker compose up -d 重建生效,用 curl http://localhost:8140/signature | grep maxSize 复核实际值(第六章)。

Q4:界面是英文/法文,想要中文? DEFAULT_LANGUAGE 只控制默认 locale(可选值受镜像内 locale 限制,官方镜像构建时只装 locales 包、启动时按该变量 locale-gen)。界面语言实际由浏览器 Accept-Language 与 ?lang=/LANGUAGE Cookie 决定:访问 http://localhost:8140/signature?lang=fr 实测返回法文界面(Signer un PDF),响应头带 Set-Cookie: LANGUAGE=fr。中文(zh)不在官方 15 种支持语言内,需自行到 hosted.weblate.org 参与翻译。

Q5:/administration 管理页打不开,返回 You (x.x.x.x) are not allowed to access this file? 管理页默认只放行 localhost、127.0.0.1、::1 三个来源 IP(源码 Config.class.php 硬编码合并进 ADMIN_AUTHORIZED_IP)。经宿主端口映射访问时来源 IP 是 Docker 网关地址(本文环境为 10.89.60.1),需在 config.ini 的 ADMIN_AUTHORIZED_IP 追加授权 IP(空格分隔)。配置方法与生效实测见 《Signature PDF 1.11.0 配置参数详解》。

Q6:多人签名的 PDF 什么时候从服务器消失? 两条路径:① 发起人持管理密钥删除(GET /signature/<hash>/delete/<adminkey>,实测 302 且目录、.admin、.expire 三者即刻清除);② 保留期到期后由 /cron 清理(第十章)。追加签名会续期。加密共享的 .gpg 文件即使被拖库也无法还原明文,但过期前始终占盘。

十二、参考链接

Signature PDF 1.11.0 Docker Compose 部署指南 - Quzuo