한 문장으로 이 프로젝트가 뭘 하는지부터.
Penpot = "팀이 함께 쓰는 디자인 스튜디오인데, 그 도면이 곧 코드라서 개발자와 AI가 바로 읽고, 통째로 내 서버에 둘 수 있는 것." 화면·앱 UI를 그리고(디자인), 클릭하면 화면이 넘어가게 만들고(프로토타이핑), 여러 명이 동시에 편집한다. 여기까진 Figma와 같다. 다른 점은 전부 오픈소스라 내 컴퓨터/회사 서버에 직접 설치(셀프호스팅)할 수 있고, 디자인 결과가 SVG·CSS·HTML이라는 웹 표준으로 표현돼 "그림"이 아니라 "코드에 가까운 도면"이라는 점이다.
Figma는 멋진 임대 작업실이다. 편하지만 열쇠는 집주인이 쥐고 있고, 월세·약관·정책이 바뀌면 따라야 한다. 작업물도 결국 그들의 창고에 쌓인다.
Penpot은 같은 수준의 작업실을 내 땅에 직접 짓는 선택지다. 설계도(소스코드)가 공개돼 있어 누구나 지을 수 있고, 내가 자물쇠를 쥔다. 게다가 이 공방의 도면은 치수가 코드로 적힌 도면이라, 시공팀(개발자)과 로봇 조수(AI)가 도면을 번역 없이 바로 읽고 시공할 수 있다.
color.primary = #6c4ad6). 디자인과 코드가 같은 "단일 진실원"을 보게 해, 브랜드 일관성과 협업을 높인다. Penpot은 이를 디자인 툴 최초로 네이티브 지원해 화제가 됐다.정리하면 Penpot의 핵심 약속은 셋이다. ① 오픈소스 + 셀프호스팅으로 디자인 인프라를 통째로 소유한다(벤더 락인 없음). ② 디자인을 코드(웹 표준)로 표현해 개발자·AI가 그대로 읽는다(design-to-code). ③ 실시간 협업·디자인 토큰·플러그인·API·MCP로 팀과 시스템 규모로 확장한다. "디자인과 코드를 잇는 풀스택 디자인 플랫폼"이라는 표현이 과장이 아니다.
"Figma 대안" 수요 + "디자인=코드" 철학 + Rust 렌더 엔진의 기술 도약.
Penpot은 2024년 Figma의 가격 정책·Adobe 인수 시도(무산) 등으로 "대안"을 찾는 흐름을 타고 급성장해, 2026년 3월 기준 GitHub 스타 45,000개를 돌파하고 v2.14대까지 왔다. 단순히 "공짜 Figma"여서가 아니다. 다섯 가지를 본다.
코드가 MPL-2.0으로 공개돼 있고, Docker·Kubernetes로 내 서버에 직접 띄울 수 있다. 디자인 자산이 외부 클라우드에 묶이지 않으니, 보안·규정이 엄격한 조직(금융·공공·대기업)도 안심하고 쓴다. "디자털 공공재(Digital Public Good)"로 검증받기도 했다. 벤더 락인이 없다는 점이 가장 큰 차별점이다.
Penpot은 디자인을 SVG·CSS·HTML로 표현하고, Flexbox·CSS Grid 레이아웃을 디자인 단계에서 그대로 쓴다. 즉 디자이너가 만든 화면이 처음부터 "실제 코드처럼 반응"한다. 개발자는 Inspect 탭에서 바로 쓸 코드를 얻는다. "디자인한 그대로 출시한다"는 약속이 기술적으로 뒷받침된다.
색·간격·타이포 등을 토큰으로 관리해 디자인 시스템을 코드처럼 확장한다. 컴포넌트·배리언트(Variants)와 결합해 디자인과 개발이 같은 단일 진실원을 공유한다. 대규모 UI를 일관되게 관리해야 하는 팀에 결정적이다.
2025~2026년 AI 에이전트 붐 속에서, Penpot은 MCP 서버(@penpot/mcp)를 내놓아 AI가 디자인 파일을 직접 읽고 조작하게 했다. "디자인을 코드로 옮기는" 워크플로를 AI가 자동화할 수 있는 토대 — "디자인=코드"였기에 가능한 자연스러운 확장이다.
Penpot 2.x는 렌더링 핵심을 Rust로 다시 짜 WebAssembly로 컴파일하고, 구글의 그래픽 엔진 Skia를 통해 WebGL로 그린다(render-wasm). 복잡한 대형 디자인 파일에서도 부드럽게 동작하도록 성능 천장을 끌어올렸다. "오픈소스라 느리다"는 편견을 정면으로 깬 기술적 사건이다.
| 관점 | Figma | Penpot |
|---|---|---|
| 소유/호스팅 | SaaS 전용(클라우드 종속) | SaaS + 셀프호스팅 선택. 데이터 소유. |
| 라이선스 | 독점(상용) | 오픈소스 MPL-2.0 |
| 디자인 표현 | 독자 포맷 | 웹 표준(SVG·CSS·HTML·Flex/Grid) |
| 디자인 토큰 | 플러그인/부가 | 네이티브(최초) |
| AI 연동 | 제한적 | MCP 서버 + 공개 API/플러그인 |
| 렌더 엔진 | 독자 C++/WASM | Rust + Skia(WebGL), 오픈 |
오픈소스·셀프호스팅으로 통제권을, 웹 표준·Inspect·디자인 토큰으로 개발자 친화를, MCP·API로 AI 자동화를 동시에 노린다. 게다가 Rust/Skia로 성능까지 챙겨, "대안"을 넘어 "더 나은 선택"을 주장할 근거를 갖췄다.
직접 띄우면 PostgreSQL·Redis·스토리지를 운영·백업해야 한다(SaaS의 편함은 포기). 플러그인·디자인 리소스 생태계는 Figma에 비해 아직 작고, 고급 기능·일부 디테일은 따라잡는 중이다. 또 내부가 Clojure/ClojureScript + Rust/WASM라 기여하려면 진입장벽이 높다. "공짜"라기보다 "소유의 대가로 운영 책임을 지는" 도구로 이해하는 게 정확하다.
deps.edn · package.json · Cargo.toml · docker/ 를 실제로 열어 확인한 구성.
Penpot은 한 언어로 짠 앱이 아니라 "역할별로 가장 잘 맞는 도구를 모은 모노레포"다. 크게 ⓐ 백엔드(Clojure), ⓑ 공통 코드(CLJC), ⓒ 프론트엔드(ClojureScript), ⓓ 렌더 엔진(Rust/Skia), ⓔ 익스포터, ⓕ MCP 서버, ⓖ 인프라로 나뉜다. 핵심 통찰은 "프론트와 백엔드가 같은 데이터 모델 코드를 공유한다"는 점(CLJC)과 "성능이 필요한 렌더링만 Rust로 분리"했다는 점이다.
| 구성요소 | 무엇 | 역할 |
|---|---|---|
| Clojure 1.12 (JVM) | 함수형 Lisp 언어 | 백엔드 본체. deps.edn으로 의존성 관리. |
| yetti (funcool) | HTTP 서버 | Jetty 기반 경량 웹 서버. 요청 수신. |
| reitit | 라우팅 | RPC 엔드포인트(명령/쿼리) 경로 매핑. |
| Integrant | 컴포넌트 시스템(DI) | DB·Redis·HTTP 등을 "부품"으로 묶어 시작/종료 관리. |
| next.jdbc + HikariCP | DB 접근 + 커넥션풀 | PostgreSQL 질의. 빠른 풀링. |
| lettuce-core | Redis 클라이언트 | 캐시 + msgbus(실시간 pub/sub). |
| buddy | 인증/암호 | 비밀번호 해시·세션·서명(JWT 등). |
| AWS SDK S3 | 오브젝트 스토리지 | 이미지·파일 저장(또는 로컬 FS). |
| Prometheus | 메트릭 | 운영 모니터링 지표 노출. |
common/은 Clojure와 ClojureScript에서 동시에 컴파일되는 .cljc 코드다. 디자인 데이터 모델, 변경(change) 로직, 검증 스키마를 한 번만 정의해 양쪽이 공유한다. 핵심 라이브러리: malli(스키마/검증), transit(데이터 직렬화), promesa(비동기), fressian/data.json(직렬화). 이 "한 모델, 양쪽 사용"이 Penpot 아키텍처의 비밀병기다.
| 구성요소 | 무엇 | 역할 |
|---|---|---|
| ClojureScript 1.12 | JS로 컴파일되는 Clojure | 웹 앱 본체(frontend/src/app). |
| shadow-cljs | CLJS 빌드 도구 | main·worker·storybook 번들 생성. |
| React (rumext 래퍼) | UI 렌더 | 컴포넌트 트리. CLJS에서 React 사용. |
| Web Worker | 백그라운드 스레드 | 무거운 계산을 메인 스레드와 분리. |
| SCSS + Storybook | 스타일 + UI 카탈로그 | 디자인 시스템 컴포넌트 문서화. |
| Playwright + Vitest | E2E·단위 테스트 | 브라우저 자동화 테스트. |
| 구성요소 | 무엇 | 역할 |
|---|---|---|
| Rust 2021 | 시스템 언어 | 렌더링 핵심. render_wasm 바이너리. |
| skia-safe | Skia의 Rust 바인딩 | 구글 그래픽 엔진 Skia로 도형·텍스트·이미지 그리기(gl·svg·textlayout·pdf 기능). |
| Emscripten | C/C++→WASM 툴체인 | Rust+Skia를 브라우저에서 도는 WebAssembly로 컴파일. |
| glam · bezier-rs | 수학 · 곡선 | 벡터·행렬 연산, 베지어 곡선 처리. |
| gl | OpenGL/WebGL 바인딩 | GPU 가속 캔버스 출력. |
익스포터(exporter/)는 ClojureScript로 짜였고 Playwright(헤드리스 크로뮴)로 디자인을 실제 렌더해 PNG·PDF·SVG로 내보낸다(ioredis로 작업 큐 연동). MCP 서버(mcp/)는 유일하게 TypeScript로 작성된 pnpm 워크스페이스(@penpot/mcp v2.16)로, AI가 디자인을 다루는 5개 도구를 제공한다. 인프라는 docker/ 아래 개발환경(devenv)과 배포 이미지(backend·frontend·exporter·mcp·storybook)를 각각 Dockerfile로 정의하고, 전체는 pnpm 모노레포 + Clojure CLI로 묶인다.
Clojure 백엔드는 공방의 본부(주문·자재·기록 관리), CLJC 공통은 본부와 매장이 똑같이 보는 "표준 도면 규격집", CLJS 프론트는 손님이 쓰는 매장(편집 화면), Rust 렌더 엔진은 그 매장 뒤에서 초고속으로 그림을 찍어 내는 인쇄기, 익스포터는 완성본을 파일로 포장하는 배송실, MCP는 로봇 조수(AI)가 드나드는 전용 출입구다.
전체 그림을 한 장으로 본 뒤, "실시간 협업"과 "Rust 렌더링" 두 흐름을 끝까지 따라간다.
Penpot이 푸는 핵심 문제는 둘이다. (A) 여러 명이 같은 디자인을 동시에 편집해도 어긋나지 않게 하는 것, (B) 복잡한 벡터 그래픽을 브라우저에서 빠르게 그리는 것. 전자는 Clojure 백엔드 + Redis msgbus로, 후자는 Rust/Skia 렌더 엔진으로 푼다. 먼저 숲을 보자.
디자이너 A와 B가 같은 파일을 열고 있을 때, A가 버튼 색을 바꾸면:
// A의 브라우저
1. 편집 A가 버튼 색 변경 → change 객체 생성(공통 데이터 모델)
2. 전송 WebSocket으로 백엔드에 change 전송
// 서버 (Clojure 백엔드)
3. 적용 RPC 명령이 change를 PostgreSQL의 파일 상태에 반영
4. 방송 msgbus(Redis pub/sub)로 "이 파일 구독자 전원"에게 change 발행
// B의 브라우저
5. 수신 WebSocket으로 change 수신 → 로컬 상태에 동일 적용
6. 재렌더 바뀐 도형만 render-wasm으로 다시 그림 → B 화면에도 즉시 반영
핵심은 A와 B와 서버가 같은 change 정의(common .cljc)를 공유한다는 점이다. 프론트가 만든 change를 백엔드가 "번역 없이" 그대로 이해하고, 다시 다른 프론트가 그대로 적용한다. 언어가 같으니(Clojure/ClojureScript + 공통 모델) 직렬화·해석 비용과 버그가 크게 준다.
2.x의 가장 큰 기술 변화. 도형을 그릴 때 JS만으로 처리하던 것을 Rust/Skia로 넘긴다(서버 플래그 또는 ?wasm=true로 활성화):
왜 이렇게 하나? JS는 편리하지만 대량의 도형·픽셀 연산엔 느리다. 무거운 그리기를 Rust(네이티브급 속도)와 Skia(검증된 그래픽 엔진)에 맡기고, JS↔WASM 사이는 선형 메모리에 바이너리로 직접 주고받아 변환 비용을 최소화한다. 그래서 거대한 디자인 파일도 부드럽다.
Penpot의 진짜 무게중심은 Clojure 계열 + Rust/WASM이다. 기여하거나 깊이 이해하려면 ① ClojureScript와 ② Rust/Emscripten 빌드를 둘 다 만나게 된다. 또 렌더 엔진(WASM)은 선택적 활성화 단계를 거쳐 왔으므로, "기본값이 항상 Rust 경로"라고 단정하지 말고 버전·플래그를 확인해야 한다.
모노레포라 폴더가 곧 "역할"이다. 큰 것만 본다.
최상위 폴더 하나하나가 앞서 본 스택의 한 조각이다. "어디를 열면 무엇이 있는지"를 지도로 익혀 두면 소스 탐색이 훨씬 쉬워진다.
| 위치 | 한 줄 역할 |
|---|---|
| backend/src/app/rpc | API의 심장. 클라이언트가 부르는 명령/쿼리 정의. |
| backend/src/app/msgbus.clj | Redis pub/sub로 실시간 변경을 전 구독자에 방송. |
| common/ | 프론트·백엔드 공유 데이터 모델·change·malli 스키마(.cljc). |
| frontend/src/app/main | 실제 편집기 화면(React/rumext) — 사용자가 만지는 곳. |
| frontend/src/app/render_wasm | CLJS ↔ Rust 렌더러를 잇는 다리(선형 메모리 패킹). |
| render-wasm/src/render | Rust 렌더 핵심. Skia로 도형/텍스트 그리기. |
| exporter/ | Playwright로 디자인을 파일(PNG/PDF/SVG)로 내보냄. |
| mcp/ | AI가 디자인을 읽고 조작하는 MCP 서버(TypeScript). |
| docker/ | 개발환경(devenv) + 서비스별 배포 이미지 정의. |
이 레포 하나로 "함수형 풀스택 + Rust/WASM 그래픽 + 실시간 협업 + AI 연동"을 한꺼번에 본다.
Penpot은 단일 기술 예제가 아니라 현대 웹의 어려운 문제들이 한자리에 모인 종합 교과서다. 기술별로 무엇을 건질지 짚는다.
프론트와 백엔드가 common/의 .cljc 데이터 모델·검증을 공유하는 구조를 직접 본다. 코드 중복·직렬화 버그를 줄이는 함수형 풀스택의 모범. Lisp 계열·불변 데이터·malli 스키마를 실무 규모로 학습.
실습: common/에서 change(변경) 데이터 정의를 찾아, 프론트와 백엔드가 각각 어디서 그것을 쓰는지 추적.
무거운 렌더링을 Rust로 분리하고 Emscripten으로 WASM 컴파일, JS↔WASM을 선형 메모리로 잇는 고성능 패턴을 본다. skia-safe로 Skia를 다루는 실전 그래픽 코드도 함께. 게임·에디터·시각화에 그대로 응용 가능.
실습: render-wasm/src/shapes를 읽고, 도형 하나가 Skia로 그려지는 경로를 따라가기.
"작은 change 단위 + pub/sub 방송"으로 여러 사용자를 동기화하는 설계(msgbus.clj)를 본다. 채팅·문서 공동편집·실시간 대시보드 등 모든 협업 앱의 핵심 패턴을 실제 코드로 학습.
실습: change가 생성→전송→DB반영→방송→수신되는 한 사이클을 코드로 추적.
색·간격을 토큰으로 추상화하고 컴포넌트/배리언트로 재사용하는 디자인 시스템 엔지니어링을 본다. 디자인과 개발의 단일 진실원을 코드 수준에서 어떻게 만드는지 배운다.
실습: Penpot에서 디자인 토큰을 만들고, Inspect 탭에서 그것이 CSS 변수로 어떻게 나오는지 확인.
TypeScript로 짜인 mcp/를 통해 "디자인 파일을 AI가 다루는 도구로 노출"하는 법을 본다. AI 시대의 디자인-개발 워크플로가 실제로 어떻게 구현되는지의 살아 있는 예.
실습: mcp/의 도구 정의를 읽고, AI에게 노출되는 5개 도구가 각각 무엇을 하는지 정리.
"그냥 써보기"는 Docker 한 방. "개발·기여"는 Clojure+Node+Rust 삼종 세트.
Penpot은 추론 모델이 아니라 웹 서비스라 특수 GPU가 필요 없다(렌더링의 WebGL은 사용자 브라우저에서 동작). 다만 셀프호스팅하려면 데이터·캐시·스토리지 인프라가, 소스를 빌드·기여하려면 세 개의 툴체인이 필요하다.
| 용도 | 요구사항 |
|---|---|
| 그냥 써보기(셀프호스팅) | Docker + Docker Compose. 공식 compose로 한 번에 기동. Kubernetes·Elestio도 지원. |
| 데이터베이스 | PostgreSQL(파일·상태 저장) — 운영 시 백업 필수. |
| 캐시/실시간 | Redis(캐시 + msgbus 실시간 협업). |
| 스토리지 | AWS S3 또는 로컬 파일시스템(이미지·자산). |
| 개발: 백엔드 | Clojure CLI + JDK(JVM). deps.edn 기반. |
| 개발: 프론트 | Node.js + pnpm + shadow-cljs(ClojureScript 빌드). |
| 개발: 렌더 엔진 | Rust + Emscripten(WASM 빌드). Skia 빌드 캐시 필요. |
| GPU | 서버엔 불필요. 클라이언트는 WebGL 지원 브라우저면 충분. |
가장 빠른 길은 공식 Docker Compose로 SaaS와 동일한 스택을 통째로 띄우는 것이다. 소스 빌드는 docker/devenv가 개발 컨테이너로 Clojure·Node·Rust 환경을 미리 갖춰 주니, 로컬에 일일이 깔지 않아도 된다.
"띄우기"는 30분, "소스 읽기·빌드"는 며칠. 난이도 순으로.
공식 Docker Compose로 Penpot을 로컬에 기동하고 계정을 만들어 첫 디자인을 그려 본다. PostgreSQL·Redis가 함께 뜨는 것을 확인.
버튼·카드를 그린 뒤 Inspect 탭에서 SVG·CSS·HTML 코드를 확인하고, Flex/Grid 레이아웃을 적용해 반응형으로 동작시킨다.
색·간격 토큰을 정의해 여러 컴포넌트에 적용하고, 토큰 값을 바꿨을 때 전체가 일관되게 변하는지 확인한다. 익스포터로 PNG/SVG 내보내기도 시도.
?wasm=true(또는 서버 플래그)로 render-wasm 경로를 활성화하고, 큰 디자인 파일에서 기존 경로와 체감 성능을 비교한다. 브라우저 개발자도구로 WASM 모듈 로드를 확인.
common/에서 도형·change 정의와 malli 스키마를 찾아, 같은 코드가 백엔드(rpc)와 프론트(main)에서 각각 어떻게 쓰이는지 양쪽을 대조한다.
Rust + Emscripten 환경을 갖춰 render-wasm을 빌드하고, src/shapes에서 특정 도형의 렌더 로직을 읽거나 작은 수정을 가해 결과 변화를 확인한다.
Penpot을 100% 소화하기 위한 8주 코스(주당 6~8시간 가정).
| 주차 | 주제 | 학습 자료 / 할 일 |
|---|---|---|
| 1주차 | Penpot 사용자 관점 | 셀프호스팅 기동(실습 1) + 디자인·프로토타입·Inspect 익히기(실습 2). |
| 2주차 | Clojure 기초 | 불변 데이터·함수형·REPL. 백엔드 deps.edn 구조 훑기. |
| 3주차 | ClojureScript + shadow-cljs | CLJS 빌드, React/rumext 컴포넌트. frontend/src/app/main 읽기. |
| 4주차 | 공통 모델(CLJC) · malli | common/ 데이터 모델·검증 정독. 실습 5. |
| 5주차 | 백엔드 아키텍처 | Integrant·reitit·RPC·next.jdbc. 명령/쿼리 흐름 추적. |
| 6주차 | 실시간 협업 | msgbus.clj + WebSocket. change 동기화 사이클(실습 3 병행). |
| 7주차 | Rust + WASM + Skia | render-wasm 빌드·구조. JS↔WASM 선형 메모리. 실습 4·6. |
| 8주차 | 디자인 토큰 · MCP | 토큰 시스템 + mcp/ 도구 정의. AI design-to-code 실험. |
이 문서와 레포에 등장한 용어를 한자리에.
| 용어 | 의미 |
|---|---|
| Penpot | 디자인·코드 협업을 잇는 오픈소스 디자인 플랫폼. SaaS+셀프호스팅. 제작 Kaleidos. |
| 셀프호스팅 | 내 서버에 직접 설치해 운영. 데이터·통제권 소유. 벤더 락인 없음. |
| MPL-2.0 | Mozilla Public License 2.0. 파일 단위 카피레프트 오픈소스 라이선스. |
| Clojure | JVM에서 도는 함수형 Lisp. Penpot 백엔드 언어. |
| ClojureScript | JS로 컴파일되는 Clojure. Penpot 프론트엔드 언어. |
| CLJC (.cljc) | Clojure·ClojureScript 양쪽에서 컴파일되는 코드. 공통 모델 공유. |
| shadow-cljs | ClojureScript 빌드 도구. main·worker 번들 생성. |
| Integrant | 컴포넌트(의존성) 시스템. 서버 부품의 시작/종료 관리. |
| reitit | Clojure 라우팅 라이브러리. RPC 경로 매핑. |
| malli | 데이터 스키마·검증 라이브러리. 공통 모델 검증. |
| msgbus | Redis pub/sub를 감싼 실시간 메시지 버스. 협업 변경 방송. |
| change(변경) | "도형을 옮김" 같은 작은 변경 한 조각. 동기화·이력의 단위. |
| render-wasm | Rust+Skia로 짠 캔버스 렌더 엔진. Emscripten으로 WASM 컴파일. |
| Skia / skia-safe | 구글의 2D 그래픽 엔진 / 그 Rust 바인딩. 도형·텍스트 래스터화. |
| WebAssembly(WASM) | 브라우저에서 네이티브급 속도로 도는 바이너리. 렌더 핵심 실행. |
| Emscripten | C/C++(여기선 Rust+Skia)를 WASM으로 컴파일하는 툴체인. |
| 선형 메모리 | WASM의 연속 메모리 공간. JS↔WASM이 바이너리로 직접 주고받는 통로. |
| 디자인 토큰 | 색·간격 등에 이름을 붙인 디자인 변수. 디자인·코드의 단일 진실원. |
| Inspect 모드 | 디자인 요소의 SVG/CSS/HTML 코드를 바로 보여 주는 기능. |
| MCP | Model Context Protocol. AI가 외부 도구·데이터에 접속하는 표준("AI용 USB 포트"). |
| exporter | Playwright(헤드리스 브라우저)로 디자인을 PNG/PDF/SVG로 내보내는 서비스. |
원문·문서·관련 기술 더 파기.