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
排查手段与关键结论:
-
用 exporter 容器内的 Playwright 手动加载渲染页(复现渲染过程):
- 发现
/js/config-render.js返回 404 (镜像中缺失,只有config.js)------经检查render.js并不引用它,非致命,仅告警; - 发现 API 返回 401 ------ 这是测试脚本未注入认证 cookie 导致的误判 (真实 exporter 会注入
auth-tokencookie)。
- 发现
-
看前端 nginx 访问日志(真实 exporter 的请求):
get-page、get-font-variants均返回 200(认证正常,数据正常下发);- 但单个对象从打开
render.html到出结果需 8~15 秒 ,超过 exporter 等待上限 10 秒,最后一个请求以 499(客户端超时关闭)结束。
-
反编译 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 并挂载持久化 |
六、后续建议
- 强烈建议给虚拟机增加内存至 4GB+(当前 1.9GB,swap 占用 700MB+,是渲染慢的根源;加内存后 10s 超时大概率够用,可考虑恢复默认)。
- 若未使用 MCP 功能,可停用
penpot-mcp容器,释放少量资源。 - 升级镜像或重新部署后,需重新检查
exporter-overrides/app.js挂载与超时值是否仍然有效。 - 保留的备份文件:
/data/penpot/docker-compose.yaml.bak、docker-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