尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Figma 之外的选择:用 Penpot 自托管并接入 AI 的设计工作台搭建记录

发布时间:2026/9/27 5:39:53

资讯中心
01
ARTICLE

Figma 之外的选择:用 Penpot 自托管并接入 AI 的设计工作台搭建记录

Figma 之外的选择:用 Penpot 自托管并接入 AI 的设计工作台搭建记录
为什么我会去看 Penpot去年开始我们团队在设计工具上的成本变得有点尴尬。设计席位是按人头的但真正每天打开设计稿的只有三四个人开发和产品只是偶尔进去看标注、导出切图。按席位买不划算按查看者买又有一堆权限上的限制。这个背景下我把几个方向都看了一遍继续用 Figma 免费版、换到 Sketch 加协作方案、或者干脆自托管一个。前两个方案的问题在于数据不在自己手里而我们的设计稿里有不少还没发布的产品结构法务那边对设计资产出境这件事一直有意见。Penpot 是我在这个过程里认真试过的一个。它是开源项目用 Clojure 写后端前端是 ClojureScript React 那一套协议是 MPL-2.0。它最大的特点是原生用 SVG 作为文件格式而不是自己发明一套二进制格式。这一点对后面接 AI、做自动化处理非常关键后面会展开讲。需要先说清楚这篇文章不是Penpot 全面替代 Figma的结论。两者在插件生态、社区资源、性能调优上的差距是客观存在的。我写的是如果你需要一个能自己部署、能把设计文件当结构化数据处理的工作台这条路怎么走通。部署先把一套能跑的服务立起来环境准备我用的是 Ubuntu 22.04Docker 24.xDocker Compose v2。Penpot 官方提供了一份docker-compose.yaml但直接用官方的全量版本会带一堆我暂时不需要的东西所以我做了一版裁剪过的配置。Penpot 的架构大致是四块penpot-frontendNginx 承载的前端静态资源同时反代后端penpot-backend核心 API 服务Clojure 写的penpot-exporter负责导出 PDF、图片等独立进程postgres redis数据存储和缓存对象存储可以选本地文件系统也可以接 S3 兼容服务。我一开始想省事用本地卷后来发现多实例部署时会有问题就换成了 MinIO。# docker-compose.yamlversion:3.8services:penpot-frontend:image:penpotapp/frontend:2.0.3ports:-9001:8080volumes:-penpot_assets:/opt/data/assetsdepends_on:-penpot-backend-penpot-exporterenvironment:PENPOT_FLAGS:enable-registration enable-login-with-passwordpenpot-backend:image:penpotapp/backend:2.0.3volumes:-penpot_assets:/opt/data/assetsdepends_on:-postgres-redisenvironment:PENPOT_PUBLIC_URI:http://localhost:9001PENPOT_DATABASE_URI:postgresql://penpot:penpotpostgres/penpotPENPOT_DATABASE_USERNAME:penpotPENPOT_DATABASE_PASSWORD:penpotPENPOT_REDIS_URI:redis://redis/0PENPOT_OBJECTS_STORAGE_BACKEND:s3PENPOT_OBJECTS_STORAGE_S3_ENDPOINT:http://minio:9000PENPOT_OBJECTS_STORAGE_S3_BUCKET:penpotPENPOT_OBJECTS_STORAGE_S3_ACCESS_KEY:penpotPENPOT_OBJECTS_STORAGE_S3_SECRET_KEY:penpot123PENPOT_SECRET_KEY:change-me-in-productionPENPOT_TELEMETRY_ENABLED:falsepenpot-exporter:image:penpotapp/exporter:2.0.3environment:PENPOT_PUBLIC_URI:http://penpot-frontend:8080PENPOT_REDIS_URI:redis://redis/0postgres:image:postgres:15environment:POSTGRES_INITDB_ARGS:--data-checksumsPOSTGRES_USER:penpotPOSTGRES_PASSWORD:penpotPOSTGRES_DB:penpotvolumes:-penpot_postgres:/var/lib/postgresql/dataredis:image:redis:7volumes:-penpot_redis:/dataminio:image:minio/miniocommand:server /data--console-address :9002environment:MINIO_ROOT_USER:penpotMINIO_ROOT_PASSWORD:penpot123ports:-9000:9000-9002:9002volumes:-penpot_minio:/datavolumes:penpot_assets:penpot_postgres:penpot_redis:penpot_minio:几个容易出问题的地方PENPOT_PUBLIC_URI必须和你实际访问的地址一致。我一开始写的是容器名penpot-frontend结果前端能打开但登录后一直报 CORS。原因是前端会用这个 URI 拼回调地址浏览器侧访问不到容器名。改成http://localhost:9001就正常了。PENPOT_SECRET_KEY不要用默认值。这个值用于签名 token泄露会导致会话被伪造。生产环境建议用openssl rand -base64 32生成一个。MinIO 的 bucket 需要提前创建。Penpot 不会自动建 bucket我第一次启动时后端日志一直报NoSuchBucket。进 MinIO 控制台9002 端口手动建一个penpotbucket 就行。启动dockercompose up-ddockercompose logs-fpenpot-backend看到INFO main - Started server on port 6060之类的日志说明后端起来了。访问http://localhost:9001应该能看到登录页。关于版本我用的镜像是2.0.3。Penpot 的版本迭代比较快2.x 之后引入了新的组件系统和设计 token 支持API 也有一些变化。具体到某个小版本的行为我没有逐一验证如果你的版本和我不一样遇到问题优先看官方 release notes。导入和导出先确认数据是能带走的自托管工具最怕的是进来容易出去难。Penpot 在这块做得还算开放导出单个文件可以导出.penpot本质是 zip 包里面是 JSON SVG 资源也可以导出为 SVG、PNG、PDF导入支持.penpot、.svg、.figFigma 文件.fig导入这个能力值得说一下。Figma 的文件格式官方没公开Penpot 是通过逆向做的兼容层所以复杂组件的还原度不一定完美。我拿团队一个中等复杂度的设计稿试过自动布局、变体这些基本能保留但一些用了 Figma 特有插件的节点会变成静态图层。这一点我没有找到可靠的官方兼容性矩阵如果你要迁移大量文件建议先拿几个代表性文件做验证不要一把梭。.penpot文件的结构我可以确认因为它就是 zipunzip-lsample.penpot# manifest.json# files/xxx.json - 页面和图形数据# images/xxx.png - 引用的位图files/xxx.json里是结构化的图形树每个 shape 有id、type、x、y、width、height、fills这些字段。这个结构对做自动化处理非常友好——你可以直接写脚本批量改颜色、改文案而不需要打开 GUI。接 AI三条路径我选了哪条接 AI这个说法太宽泛了。落到具体场景无非是几种根据文字描述生成设计稿初稿对现有稿子做批量修改改文案、换配色、调间距从设计稿生成代码或组件这三条路的实现难度差别很大。我逐个说。路径一插件PluginPenpot 有插件系统插件运行在沙箱里通过penpot全局对象和宿主通信。写一个插件大致是这样// plugin.jspenpot.ui.open(AI 助手,ui.html);penpot.ui.onMessage(async(msg){if(msg.typegenerate){constshapespenpot.currentPage.findShapes({type:board});// 调你自己的后端constresawaitfetch(http://localhost:8000/generate,{method:POST,headers:{Content-Type:application/json},body:JSON.stringify({prompt:msg.prompt}),});constdataawaitres.json();// 根据返回结果创建图形data.shapes.forEach((s){penpot.createRectangle();});}});这里我要坦白一点Penpot 的插件 API 在 2.x 期间变动比较频繁上面这段代码里的penpot.currentPage.findShapes和penpot.createRectangle是我在文档里看到过的接口但我没有在 2.0.3 上完整跑通一个生成式插件。如果你要基于插件做建议先照着官方插件模板仓库跑一个 hello world确认你当前版本的 API 长什么样。插件的优势是能直接操作用户当前打开的文件体验最顺。缺点是调试麻烦而且受沙箱限制网络请求要走宿主代理。路径二REST API 脚本Penpot 后端暴露了一套 REST API。核心的几组POST /api/rpc/command/login-with-password— 登录拿 tokenGET /api/rpc/command/get-file— 拿文件数据POST /api/rpc/command/update-file— 改文件注意路径里的rpc/commandPenpot 用的是自己的一套 RPC 风格不是标准的 RESTful 资源路径。用 Python 调一次登录# penpot_client.pyimportrequests BASEhttp://localhost:9001deflogin(email:str,password:str)-str:resprequests.post(f{BASE}/api/rpc/command/login-with-password,json{email:email,password:password},headers{Content-Type:application/json},)resp.raise_for_status()dataresp.json()# 返回里带 token同时 Set-Cookie 里也有 auth-tokenreturndata.get(token)orresp.cookies.get(auth-token)if__name____main__:tokenlogin(youexample.com,your-password)print(token[:20],...)拿到 token 之后get-file需要传file-id和project-iddefget_file(file_id:str,project_id:str,token:str)-dict:resprequests.get(f{BASE}/api/rpc/command/get-file,params{id:file_id,project-id:project_id},headers{Authorization:fToken{token}},)resp.raise_for_status()returnresp.json()这里有个坑get-file返回的是完整的文件数据一个大文件可能几 MB而且结构是 Penpot 内部的变更日志格式changes数组不是直接给你一棵干净的树。要拿到当前状态需要按顺序 replay 这些 change。我一开始以为能直接拿到渲染后的结构看完文档才明白得自己处理。如果你只是想读数据更省事的做法是导出.penpot包然后解 zip前面说过那个 JSON 更直观。路径三绕开 API直接处理导出文件这是我最后实际采用的方式因为它最稳。流程是这样用户在 Penpot 里设计完导出.penpot文件文件传到我的服务服务解 zip读files/*.json做结构化处理处理结果生成新的 shape 定义写回一个新 zip用户重新导入用 Python 处理# process_penpot.pyimportjsonimportzipfilefrompathlibimportPathfromioimportBytesIOdefload_penpot(path:str)-dict:读取 .penpot 包返回 manifest 和所有页面数据result{manifest:None,files:{}}withzipfile.ZipFile(path)aszf:fornameinzf.namelist():ifnamemanifest.json:result[manifest]json.loads(zf.read(name))elifname.startswith(files/)andname.endswith(.json):keyPath(name).stem result[files][key]json.loads(zf.read(name))returnresultdefcollect_text_shapes(node:dict,acc:list)-None:递归收集所有文本节点ifnode.get(type)text:acc.append(node)forchildinnode.get(shapes,[]):collect_text_shapes(child,acc)defrewrite_texts(page:dict,mapping:dict[str,str])-None:按 id 替换文本内容defwalk(node):ifnode.get(type)textandnode.get(id)inmapping:# 注意Penpot 的文本内容是富文本结构不是纯字符串contentnode.get(content,{})ifchildrenincontent:forparaincontent[children]:forspaninpara.get(children,[]):ifspan.get(text)inmapping[node[id]]:span[text]mapping[node[id]]forchildinnode.get(shapes,[]):walk(child)walk(page)注意最后那个rewrite_texts。Penpot 的文本节点内容不是简单的text: hello而是一个富文本树段落和 span 嵌套。这一点我是解包看了实际结构才确认的官方文档里没有特别清楚的说明。所以替换文案不能直接赋值得沿着这棵树改。这也解释了为什么路径三其实没那么简单——你以为只是改个字符串实际要理解它的数据结构。但好处是可控不依赖 API 稳定性。把 LLM 接进来有了结构化的 shape 数据接 LLM 就变成普通的 JSON 处理问题。比如做根据描述生成一个简单布局# ai_layout.pyimportjsonfromopenaiimportOpenAI clientOpenAI(api_keyyour-key,base_urlhttps://api.openai.com/v1)SYSTEM你是一个 UI 布局助手。用户会描述一个界面你需要输出一个 JSON 数组 每个元素代表一个矩形字段包括 x, y, width, height, name。 坐标以左上角为原点单位像素。画布宽度固定 1440。defgenerate_layout(prompt:str)-list[dict]:respclient.chat.completions.create(modelgpt-4o-mini,messages[{role:system,content:SYSTEM},{role:user,content:prompt},],response_format{type:json_object},)contentresp.choices[0].message.content datajson.loads(content)returndata.get(shapes,[])if__name____main__:shapesgenerate_layout(一个登录页包含标题、两个输入框、一个按钮)forsinshapes:print(s)然后把这些矩形转换成 Penpot 的 shape 定义塞进.penpot包。这一步需要构造符合 Penpot schema 的 JSON字段名要和它内部一致。这里我必须说明上面这段代码我只验证了 LLM 能返回结构合理的 JSON没有验证生成的 shape 定义能被 Penpot 2.0.3 正确导入。构造合法的 Penpot shape JSON 需要对它的 schema 有完整了解我目前只覆盖了矩形这种简单类型。文本、路径、分组这些还没试。如果你要做完整方案建议的做法是先在 Penpot 里手动画一个矩形导出.penpot看它生成的 JSON 长什么样然后照着这个模板填数据。这比猜字段名靠谱得多。一些实际用下来的感受性能。自托管之后性能取决于你的机器。我给的是 4C8G几十个中等复杂度的文件切换还算流畅但和 Figma 那种 CDN 加速的体验没法比。如果团队在多地办公还得考虑网络延迟。协作。Penpot 支持多人实时协作走的是 WebSocket。自托管场景下需要保证 WebSocket 不被反代截断。我用 Nginx 反代时加了proxy_set_header Upgrade和Connection upgrade否则光标同步会失效。字体。这是个容易忽略的点。Penpot 前端用的是浏览器本地字体服务端导出 PDF 时用的是容器里的字体。如果你用了自定义字体需要把字体文件挂进 exporter 容器否则导出会 fallback 到默认字体。这一点我是在导出 PDF 时发现字体不对才注意到的。插件生态。这是 Penpot 目前最明显的短板。Figma 社区里那些设计系统同步、内容填充、无障碍检查的插件Penpot 这边要么没有要么是社区早期版本。如果你团队重度依赖插件工作流迁移成本要认真评估。什么情况下值得走这条路我自己的判断是这样如果你的核心诉求是设计资产私有化和设计文件可编程Penpot 这条路是通的。它开放的格式和 API 让用脚本处理设计稿这件事从不可能变成可能这对做 AI 辅助设计的团队很重要。如果你的核心诉求是和现有 Figma 工作流无缝衔接、大量使用社区插件、追求极致的设计体验那自托管 Penpot 现在还不是替代品更像是补充。还有个现实问题自托管意味着你要自己维护。数据库备份、版本升级、对象存储容量、证书续期这些都是你的活。如果团队里没有能接手这些的人用 SaaS 反而更省心。最后留一个我还没解决的问题Penpot 的组件component和设计 token 在导出 JSON 里是怎么表示的我还没仔细看。如果要做到AI 生成的内容自动套用团队设计规范这一块是绕不过去的。等我把这块摸清楚了再写一篇。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。