把 Penpot 私有化跑在自有服务器上:Docker 部署到可上生产的实操笔记
【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot
Penpot 是目前少见的完全开源的设计协作平台,设计师在上面画图,开发能直接拿到对应的 CSS 和 SVG。这篇笔记带你把它私有化跑在自己的服务器上,数据全部留在自己手里。
部署 Penpot 前先定三件事
先想清楚这个实例给谁用。三五个人的内部小组和几十人的公司团队,在注册策略和访问控制上做法不一样:前者可以开着注册入口先跑,后者通常要关掉注册、按需建账号,再考虑接公司现有的登录体系。
然后是这台机器。Docker 20.10 以上、Docker Compose 2.0 以上是底线,内存给到 4GB 起步,生产上建议 8GB;磁盘留出 10GB 可用空间,其中要装下镜像、PostgreSQL 数据卷和用户传进来的素材。够不够用,跑一两周看内存曲线比空想靠谱。
第三件事是数据放哪。Penpot 的全部持久化数据都在两个 Docker 卷里:一个给 PostgreSQL(账号、文件、设计内容),一个给素材文件(图片、SVG 资源),默认落在文件系统后端。文件多了、机器不止一台时,可以整体切到 S3 兼容存储,这也是后面做备份和扩容的基础。
一条主线:用 Docker Compose 把 Penpot 跑起来
整个主路径就四步:拿配置、定版本、起容器、验证。先把生产用的 compose 文件从仓库里拿出来,它已经把 PostgreSQL、Redis(Valkey)、导出器这些依赖都编排好了。
git clone https://gitcode.com/GitHub_Trending/pe/penpot cd penpot cp docker/images/docker-compose.yaml docker-compose.yaml然后固定版本再启动。锁住版本的好处是升级节奏在你手里:
PENPOT_VERSION=2.4.3 docker compose -p penpot up -d启动完成后访问 http://localhost:9001 能看到登录页就说明主链路通了。另外这套配置里带了一个 mailcatcher 服务,浏览器打开 http://localhost:1080 能看见 Penpot 发出的所有邮件,验证注册流程时很好用,只是它替代不了真实 SMTP。
有一处别漏配:PENPOT_PUBLIC_URI。默认值是http://localhost:9001,一旦你要用域名访问,就改成实际对外地址,否则前端生成资源链接会按 localhost 拼,页面能打开但图片全 404。所有可配项都在 配置说明 里有完整清单。
如果你的环境是 Kubernetes,主流程也是同一套:backend、frontend、exporter 各建一个 Deployment,数据库连接串放 Secret 里,镜像同样锁版本号,剩下的事交给集群做高可用和扩缩容。如果你是打算改 Penpot 源码的开发者,可以直接用docker/devenv下的开发环境,compose 起完容器后前端在 9001 端口、后端 REPL 在 3447 端口,带热重载,两条路各花几分钟就能确认环境没问题。
跑通之后还有几个开关值得知道。想接 Google、GitHub 这类第三方登录,在PENPOT_FLAGS里加对应标志再配上 client id/secret 即可;团队设置里还能开 Webhooks,文件有更新时自动回调你的 CI 或通知渠道,实现自动化工作流。
访问令牌是 API 自动化的入口,在个人设置里生成后,就能用 curl 调 rpc 接口做导出、用户管理这类脚本化操作。
curl -X POST "https://your-penpot.com/api/rpc/command/create-access-token" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <session-token>" \ -d '{"name": "CI/CD Token", "expires-at": null}'上线前的三处硬伤:密钥、HTTPS、真实邮件
第一处是安全密钥。compose 文件里的PENPOT_SECRET_KEY默认值就是一个写着"请改我"的明文字符串,而 http session、邀请链接这些子系统的密钥都由它派生。不换的话,任何拿到同一份默认配置的人理论上都能伪造会话。换掉它只要两步:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"把输出填进PENPOT_SECRET_KEY,重启容器生效。
第二处是 HTTPS。不挂 TLS 直接跑 HTTP 的话,浏览器会禁用剪贴板这类只在安全上下文下开放的 API,用户复制粘贴设计内容会时好时坏。解法是前面配一层 Nginx 反代到 9001 端口,注意两个细节:/ws/路径要做 WebSocket 升级转发,client_max_body_size要和后端的 350MB 上限保持一致。
location /ws/ { proxy_pass http://localhost:9001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location / { client_max_body_size 350M; proxy_pass http://localhost:9001; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }Caddy 用户更省事,一个reverse_proxy :9001加证书路径就够了,仓库文档里两种都有完整示例。
第三处是邮件服务。默认 compose 里 SMTP 指向的是 mailcatcher,注册确认、邀请邮件全发进那个 1080 端口的假收件箱——这一步不配好,注册验证邮件会直接发不出去,新用户永远收不到激活链接。改成真实提供商时,在 backend 的环境变量里配置:
environment: PENPOT_FLAGS: enable-smtp PENPOT_SMTP_HOST: smtp.your-provider.com PENPOT_SMTP_PORT: 587 PENPOT_SMTP_USERNAME: your-username PENPOT_SMTP_PASSWORD: your-password PENPOT_SMTP_TLS: true再补一个PENPOT_SMTP_DEFAULT_FROM指定发件人地址,然后用测试账号走一遍注册,在真实收件箱里确认收到,这处才算闭环。
跑起来之后:备份、升级和日常观察的固定节奏
日常只需要看三样东西。容器状态用docker compose ps扫一眼,重点盯 backend 和 postgres 是否都 healthy;磁盘别低于 20% 空闲,素材卷涨得快;日志给个轮转上限,防止把盘写满:
logging: driver: "json-file" options: max-size: "10m" max-file: "3"想再加一道保险,可以给 backend 补个健康检查,pg_isready那套检查 compose 模板里已经内置了:
healthcheck: test: ["CMD", "curl", "-f", "http://localhost:6060/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s每周固定做一次备份并验证可恢复。数据库部分直接 dump,素材卷用临时容器打包,保留最近 7 天:
docker exec penpot-postgres pg_dump -U penpot penpot > $BACKUP_DIR/db_$DATE.sql docker run --rm -v penpot_penpot_assets:/volume -v $BACKUP_DIR:/backup alpine \ tar czf /backup/assets_$DATE.tar.gz -C /volume .升级时按这个顺序走:先跑一遍上面的备份,拉新镜像,停旧起新,最后翻启动日志确认迁移跑完。
PENPOT_VERSION=2.5.0 docker compose pull docker compose down PENPOT_VERSION=2.5.0 docker compose up -d docker compose logs --tail=100 penpot-backend版本尽量小步走,别一次跨太多个版本,大版本升级往往带有数据迁移逻辑,跳版本踩坑的概率会明显上升。更多部署细节可以看仓库里的 Docker 部署文档。
踩坑速查:四个高频问题的现象与处理
前端白屏或静态资源 404。通常是PENPOT_PUBLIC_URI和反代的实际域名没对齐,前端拿 localhost 的地址去请求带域名前缀的静态资源,全部落空。对照 Nginx 的server_name把PENPOT_PUBLIC_URI改成真实对外地址,重启 frontend 容器即可。
大文件上传报 413。后端的上限是 350MB(对应PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 367001600),但请求在反代这一层就被截了,Nginx 默认的client_max_body_size只有 10M。把反代这一项调到与后端一致,350M 或同数值,重载配置后重试。
用户频繁掉登录、刷新就回到登录页。用 HTTP 裸跑时,cookie 的 Secure 标志会让浏览器直接丢弃会话;反过来,HTTP 环境下模板默认的disable-secure-session-cookies标志又是个不安全配置。正规解法就是前面说的上 HTTPS,标志跟着恢复默认。
邮件石沉大海。先确认PENPOT_FLAGS里enable-smtp确实生效,再看后端日志里的报错,docker compose logs penpot-backend | grep -i smtp一般能直接看到是 DNS 解析、端口不通还是认证失败。mailcatcher 的 1025 端口只对容器内网开放,别再往它身上配参数了。
下一步
给个务实的落地顺序:先用 Docker Compose 在单节点跑通主链路,把密钥、HTTPS、真实 SMTP 这三处补齐,然后才谈域名开放和团队接入。单节点验证完没问题,再考虑 K8s 化或把素材迁到 S3。跑通第一周后,回头看一眼备份脚本和日志轮转是不是真的在按节奏工作——部署这件事,稳比快重要。
【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考