Penpot 导出 PDF 报错 —— 问题排查与解决记录

Penpot 导出 PDF 报错 ------ 问题排查与解决记录

日期:2026-08-24 | 服务器:192.168.36.136 | Penpot 版本:2.17.1


一、环境信息

项目 说明
服务器 192.168.36.136(Ubuntu 24.04,VMware 虚拟机)
硬件资源 2 核 CPU / 1.9GB 内存(低配,是本问题的关键背景)
部署方式 Docker Compose,目录 /data/penpot
组件版本 backend / frontend / exporter:latest(实际 2.17.1);MCP:2.17.1
容器清单 penpot-backend、penpot-frontend、penpot-exporter、penpot-postgres、penpot-redis、penpot-assets(MinIO)、penpot-mcp(共 7 个)
访问地址 http://192.168.36.136:9001

二、问题现象

前端访问 http://192.168.36.136:9001,在画布中导出 PDF 报错,导出失败。


三、排查过程

3.1 第一步:确认服务整体状态

bash 复制代码
systemctl --failed --no-pager          # 无失败单元
docker ps -a                           # 7 个容器全部 Up,postgres healthy
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9001/   # 200

结论:服务本身都在运行,问题出在导出链路内部。

3.2 第二步:定位第一个错误 ------ S3 存储配置(旧格式)

后端日志(penpot-backend)

复制代码
E app.http.errors - hint="internal error"
clojure.lang.ExceptionInfo: backend ':s3' not configured (impl.clj:212)
    app.rpc.management.exporter$sm$upload_tempfile.invokeStatic(exporter.clj:45)

W app.storage - hint="using deprecated configuration, please read 2.11 release notes"

exporter 日志(penpot-exporter)

复制代码
ERR [app.handlers.export-frames] hint="unexpected error on frames exportation"
  Data: {:type :internal, :code :unable-to-upload-resource, :response-status 500}

关键判断 :PDF 实际渲染成功 了(pdfunite 已合并 5 个分页),只是最后一步把导出结果上传到对象存储时失败(500 unable-to-upload-resource)。

根因 1 :存储配置使用的是 Penpot 2.11 之前的旧变量名/取值,新版后端无法识别:

旧格式(2.11 前) 新格式(2.11+)
PENPOT_ASSETS_STORAGE_BACKEND: "assets-s3" PENPOT_OBJECTS_STORAGE_BACKEND: "s3"
PENPOT_ASSETS_S3_BUCKET PENPOT_OBJECTS_STORAGE_S3_BUCKET
PENPOT_ASSETS_S3_REGION PENPOT_OBJECTS_STORAGE_S3_REGION
PENPOT_ASSETS_S3_ENDPOINT PENPOT_OBJECTS_STORAGE_S3_ENDPOINT
PENPOT_ASSETS_S3_ACCESS_KEY_ID AWS_ACCESS_KEY_ID
PENPOT_ASSETS_S3_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY

另外,MinIO(penpot-assets)中根本没有 assets bucket/data 下只有 .minio.sys)。

修复 1/data/penpot/docker-compose.yaml):

bash 复制代码
cd /data/penpot
cp docker-compose.yaml docker-compose.yaml.bak   # 备份

# 将 6 项存储变量批量替换为新格式
sudo sed -i \
 -e 's/PENPOT_ASSETS_STORAGE_BACKEND: "assets-s3"/PENPOT_OBJECTS_STORAGE_BACKEND: "s3"/' \
 -e 's/PENPOT_ASSETS_S3_BUCKET/PENPOT_OBJECTS_STORAGE_S3_BUCKET/' \
 -e 's/PENPOT_ASSETS_S3_REGION/PENPOT_OBJECTS_STORAGE_S3_REGION/' \
 -e 's/PENPOT_ASSETS_S3_ENDPOINT/PENPOT_OBJECTS_STORAGE_S3_ENDPOINT/' \
 -e 's/PENPOT_ASSETS_S3_ACCESS_KEY_ID/AWS_ACCESS_KEY_ID/' \
 -e 's/PENPOT_ASSETS_S3_SECRET_ACCESS_KEY/AWS_SECRET_ACCESS_KEY/' \
 docker-compose.yaml

# 创建 MinIO bucket(用 mc 客户端容器)
sudo docker run --rm --network penpot_penpot --entrypoint sh minio/mc -c \
  'mc alias set local http://penpot-assets:9000 penpot penpot123 && mc mb --ignore-existing local/assets'

# 重启后端应用新配置
sudo docker compose up -d

验证 1 :后端重启后日志不再出现 backend ':s3' not configured 与弃用警告,存储问题解决。

3.3 第三步:定位第二个错误 ------ exporter 渲染超时(10 秒硬编码)

存储修复后仍然失败,此时错误变了:

复制代码
ERR [app.handlers.export-frames] hint="unexpected error on frames exportation"
  locator.waitFor: Timeout 10000ms exceeded.
  Call log:
    - waiting for locator('#screenshot-...') to be visible
ResourceRequest timed out

排查手段与关键结论

  1. 用 exporter 容器内的 Playwright 手动加载渲染页(复现渲染过程):

    • 发现 /js/config-render.js 返回 404 (镜像中缺失,只有 config.js)------经检查 render.js 并不引用它,非致命,仅告警;
    • 发现 API 返回 401 ------ 这是测试脚本未注入认证 cookie 导致的误判 (真实 exporter 会注入 auth-token cookie)。
  2. 看前端 nginx 访问日志(真实 exporter 的请求):

    • get-pageget-font-variants 均返回 200(认证正常,数据正常下发);
    • 单个对象从打开 render.html 到出结果需 8~15 秒 ,超过 exporter 等待上限 10 秒,最后一个请求以 499(客户端超时关闭)结束。
  3. 反编译 exporter 源码/opt/penpot/exporter/app.js,ClojureScript 打包产物):

    • app.browser.wait_for 超时默认值 10000 硬编码,无环境变量可配置;
    • 渲染等待 #screenshot-<id> 元素出现时只传节点、未传超时 → 使用默认 10 秒。

根因 2 :服务器仅 2 核 CPU / 1.9GB 内存,Chromium + WASM 渲染单对象需要 8~15 秒以上(内存吃紧时 swap 压力大),超过 exporter 硬编码的 10 秒超时 → 每次导出必失败。

修复 2(把渲染等待超时 10s → 60s,并持久化):

bash 复制代码
# 1) 修改 exporter 容器内的 app.js(精确替换唯一匹配点)
sudo docker exec penpot-penpot-exporter-1 sh -c \
  "sed -i 's/cljs\$cst\$1307\$timeout, 10000/cljs\$cst\$1307\$timeout, 60000/' /opt/penpot/exporter/app.js"

# 2) 拷出到宿主机做持久挂载
sudo mkdir -p /data/penpot/exporter-overrides
sudo docker cp penpot-penpot-exporter-1:/opt/penpot/exporter/app.js /data/penpot/exporter-overrides/app.js

# 3) docker-compose.yaml 中 penpot-exporter 服务增加挂载
#    volumes:
#      - ./exporter-overrides/app.js:/opt/penpot/exporter/app.js:ro

# 4) 重启 exporter
sudo docker compose up -d penpot-exporter

# 5) 验证挂载与超时值
grep -c 'timeout, 60000' /data/penpot/exporter-overrides/app.js   # 1
docker inspect penpot-penpot-exporter-1 --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'

说明:挂载覆盖使修改在容器重建后仍然生效;备份位于容器内 app.js.bak 与宿主机 docker-compose.yaml.bak / .bak2


四、最终验证结果

时间 事件
07:42:52 → 07:43:17 渲染对象 1(cb97a269)成功,耗时约 25s
07:43:17 → 07:43:34 渲染对象 2 成功,约 17s
07:43:34 → 07:43:51 渲染对象 3 成功
07:43:51 → 07:44:06 渲染对象 4 成功
07:44:06 → 07:44:18 渲染对象 5 成功
07:44:18 pdfunite 合并 5 个分页 PDF 成功,导出完成 ✅

后端与 exporter 日志全程无 error,MinIO assets bucket 已就绪,导出功能恢复正常。


五、根因总结

# 根因 影响 修复
1 存储配置使用 Penpot 2.11 前旧格式(PENPOT_ASSETS_STORAGE_BACKEND=assets-s3),且 MinIO 缺少 assets bucket 渲染成功但上传失败(500 unable-to-upload-resource 升级为新格式变量 + 创建 bucket + 重启后端
2 服务器低配(2 核 / 1.9GB),WASM 渲染单对象需 8~15s+,超过 exporter 硬编码 10s 超时 渲染被中断,导出超时失败 将 exporter 渲染等待超时改为 60s 并挂载持久化

六、后续建议

  1. 强烈建议给虚拟机增加内存至 4GB+(当前 1.9GB,swap 占用 700MB+,是渲染慢的根源;加内存后 10s 超时大概率够用,可考虑恢复默认)。
  2. 若未使用 MCP 功能,可停用 penpot-mcp 容器,释放少量资源。
  3. 升级镜像或重新部署后,需重新检查 exporter-overrides/app.js 挂载与超时值是否仍然有效。
  4. 保留的备份文件:/data/penpot/docker-compose.yaml.bakdocker-compose.yaml.bak2、容器内 /opt/penpot/exporter/app.js.bak

七、关键命令速查

bash 复制代码
# 查看所有服务/容器
docker ps -a
docker stats --no-stream

# 后端 / exporter 日志
docker logs --tail 100 penpot-penpot-backend-1
docker logs --tail 100 penpot-penpot-exporter-1
docker logs --since 10m penpot-penpot-frontend-1 | grep -E 'get-page|401|499'

# 检查 MinIO bucket
docker run --rm --network penpot_penpot --entrypoint sh minio/mc -c \
  'mc alias set local http://penpot-assets:9000 penpot penpot123 && mc ls local'

# 验证 exporter 超时值
grep -c 'timeout, 60000' /data/penpot/exporter-overrides/app.js
相关推荐
SamChan901 小时前
PDF 翻译服务的全链路可观测性设计:OpenTelemetry + Jaeger + Loki 实战方案
后端·python·pdf·机器翻译
Source.Liu1 天前
【A11】基于 PDF 渲染引擎的统一文档界面架构设计
rust·pdf
zhonyu鱼2 天前
Saber:专为触控笔设计的开源手写笔记与 PDF 批注工具
笔记·pdf·开源
winfredzhang2 天前
用 Python + wxPython 造一个照片工具箱:PDF / 加密ZIP / MP4 / 归档,以及我在这过程中踩到的 4 个坑
python·pdf·zip·mp4·移动
2501_930707783 天前
使用C#代码在 PDF 文档中创建列表
pdf
gsls2008083 天前
Penpot Docker 部署与 MCP 服务配置文档
运维·docker·容器·原型·penpot
AI导出鸭3 天前
Claude的LaTeX生成PDF文件复制后数学公式乱码,怎样修改?专业用户首选“AI导出鸭”
人工智能·pdf·ai导出鸭
qyyyyy5704 天前
外文 PDF 怎么整理成 Markdown?从翻译、内容提取到 RAG 知识库导入
自然语言处理·pdf·erlang·机器翻译·零知识证明
AI导出鸭4 天前
Perplexity的LaTeX生成PDF文件复制后数学公式乱码,怎样修改?AI导出鸭苹果版的技术拆解与批量之道
人工智能·pdf·ai导出鸭