Penpot vs Figma: what the open-source alternative really changes
Figma and Penpot share the same paradigm: vector design in the browser, real-time collaboration, reusable components and built-in developer handoff. The fundamental difference lies in the data model and in control. Figma stores everything on its own servers, in a proprietary format. Penpot builds on SVG and CSS as its base language and runs on your infrastructure: you decide where the files live, who connects and when you update. For a team that already self-hosts its code on Gitea, its documentation on Docmost and its chat on Mattermost, hosting the design tool on ServOrbit as well is the logical next step. The price is operations: backups, updates and sizing become your job, which is what the following sections cover.
What Penpot offers out of the box
- Real-time vector design — several designers on the same file at once, visible cursors, contextual comments on layers.
- Components and shared libraries — build design systems and publish them as libraries shared across projects.
- Native developer handoff — CSS inspector, asset export, annotations and spacing measurements generated from the designs.
- Interactive prototyping — link boards with transitions and simulate navigation without leaving the tool.
- Flexible layouts — auto layouts, CSS grids and resize constraints comparable to those in Figma.
- Open formats —
.penpotexport (full archive) and per-board SVG export, readable in Inkscape or any vector editor. - MCP server — the
penpot-mcpservice exposes your files to MCP-compatible assistants (see the dedicated callout below).
Architecture: one Docker stack, a single HTTP entry point
Penpot is a stack of several containers. The Compose file published by the project declares, at version 2.18, eight services: penpot-frontend, penpot-backend, penpot-admin-console, penpot-mcp, penpot-exporter, penpot-postgres, penpot-valkey and penpot-mailpit. The ServOrbit template starts a subset depending on its version: read the exact number with docker compose ps on your VPS. The table below gives the role of each service as the upstream Compose file describes it. Only one port matters for exposure: the frontend listens internally on 8080 and the Compose file publishes it on 9001. Put your reverse proxy in front of that port and nothing else.
The services of the Penpot stack
Scroll the table
| Service | Role | Good to know |
|---|---|---|
| `penpot-frontend` | Serves the application and routes requests to the other services | The only service to expose: port 9001 in the upstream Compose file |
| `penpot-backend` | API and business logic (Java/Clojure application) | Files (assets) in a volume; SMTP and storage are configured here |
| `penpot-exporter` | PNG, PDF and SVG exports through a headless browser | Reaches the app through `PENPOT_PUBLIC_URI`: a valid certificate is required |
| `penpot-postgres` | PostgreSQL 15 database: accounts, projects, files | Volume `penpot_postgres_v15` must be backed up |
| `penpot-valkey` | Real-time notifications (websockets) and cache | Capped at 128 MB (`--maxmemory 128mb`) in the upstream Compose file |
| `penpot-mcp` | MCP server for AI assistants | Enabled by the `enable-mcp` flag, websocket `/mcp/ws` |
| `penpot-admin-console` | Administration console | Added to the stack in 2.18, flag `enable-admin-console` |
| `penpot-mailpit` | Test SMTP inbox (interface on port 8025) | Replace with a real SMTP server in production |
Sized requirements: RAM, CPU, disk, ports
The upstream documentation publishes no minimum floor for a small team. It states that 4 CPUs and 16 GB of RAM are enough to serve thousands of users, and lets you be conservative when allocating resources. For disk, it recommends starting from 50 to 100 GB of database space with elastic sizing, which covers up to 10 editors, then roughly 5 GB more per additional editor. The real size depends on usage and file complexity, not on the number of accounts. Our editorial recommendation for a team of a few people: a VPS with 4 GB of RAM and a limit per service (memory section below), which leaves headroom for exporting heavy files; 2 GB is fine for a trial. On the network side, open 80 and 443 to the reverse proxy, never 9001 or 8025 directly. The maximum request body in the Compose file is 367001600 bytes (350 MiB): the client_max_body_size directive of your proxy must follow. On the workstation side, the documentation recommends the latest stable version of Chrome.
Deployment in 8 steps
Order a ServOrbit VPS
Plan for 4 GB of RAM for team use, 2 GB for a trial. Ubuntu 24.04 LTS is the default template. Keep at least 50 GB of free disk if you import large files.
Point a subdomain at your VPS
Create an A record for
design.your-domain.compointing to the VPS IP. The ServOrbit provisioner issues a Let's Encrypt certificate. An HTTPS domain is essential: Penpot setssecuresession cookies by default, and the upstream documentation reservesdisable-secure-session-cookiesfor non-production trials.Install from the marketplace
In the ServOrbit console: Marketplace, Collaboration, Penpot, Deploy. The AWX job starts the containers, generates
PENPOT_SECRET_KEYand setsPENPOT_PUBLIC_URIto your domain. The interface answers onhttps://design.your-domain.comwithin a few minutes.Check that every service is running
Connect over SSH and run
docker compose lsto locate the Compose file, thendocker compose -p penpot -f docker-compose.yaml ps. Each service should berunning, withpenpot-postgresandpenpot-valkeyreportedhealthy. Logs are read withdocker compose -p penpot -f docker-compose.yaml logs -f.Create your account
Registration is open by default. Open
https://design.your-domain.comand click "Create an account": in the upstream Compose file email verification is disabled (disable-email-verification), so the account is active immediately. Create yours right away, before anyone else does.Close public registration
Add the
disable-registrationflag toPENPOT_FLAGS(frontend and backend) and recreate the containers. Invitations keep working as long asenable-login-with-passwordstays active. To create an account without an invitation, rundocker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile: the script requires theenable-prepl-serverflag and the container name may vary, check it withdocker ps.Connect a real SMTP server
Without SMTP, no invitation or password reset email is sent. See the next section for the variables.
Import your Figma files
Penpot does not open
.figfiles directly. The official route goes through the "Penpot Exporter" Figma plugin, which produces a.ziparchive to import into Penpot. The procedure and its limits are detailed below.
Post-install configuration: email and file storage
For email, enable enable-smtp in PENPOT_FLAGS and set on the backend PENPOT_SMTP_DEFAULT_FROM, PENPOT_SMTP_DEFAULT_REPLY_TO, PENPOT_SMTP_HOST, PENPOT_SMTP_PORT (587 in the upstream example), PENPOT_SMTP_USERNAME, PENPOT_SMTP_PASSWORD and PENPOT_SMTP_TLS. The upstream Compose file points to penpot-mailpit by default, which keeps messages in memory and loses them when the container is recreated. In production, also remove disable-email-verification, which the documentation advises against outside trials. For files, the images and SVG clips uploaded by users are stored on the file system by default, in the penpot_assets volume mounted at /opt/data/assets (PENPOT_OBJECTS_STORAGE_BACKEND: fs). S3-compatible storage is possible with PENPOT_OBJECTS_STORAGE_BACKEND: s3, a bucket, an optional endpoint and the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Since version 2.11.0 the old PENPOT_STORAGE_ASSETS_* names have been replaced by PENPOT_OBJECTS_STORAGE_*: if you reuse an old Compose file, rename them.
Backup and updates
A Penpot instance has two pieces of state to back up together: the PostgreSQL database and the files volume. The upstream documentation warns that you cannot directly copy the data folder of a Docker volume, and points to Docker's volume backup procedure. Our approach: a logical export of the database with docker exec penpot-penpot-postgres-1 pg_dump -U penpot penpot > penpot.sql, then an archive of the files volume through a temporary container that mounts it read-only. Find the volume names with docker volume ls. Take both at the same moment, otherwise the database may reference files missing from the archive, and test a restore on another VPS before you need it. For updates, pin the version with PENPOT_VERSION rather than following the latest image, run docker compose -p penpot -f docker-compose.yaml pull then up -d, and move in small steps: the documentation advises against jumping between distant versions. As of 1 November 2026, the latest published version is 2.18.5. If you maintain your own Compose file, 2.18 added the penpot-admin-console service and the PENPOT_ADMIN_CONSOLE_URI variable on the frontend and the backend. Always back up before updating.
Memory: what issue #3299 reports and how to cap it
Penpot issue #3299, opened in June 2023, is still open and received a message in October 2026. It gathers reports of high RAM use on Docker installations: one user reports about 20 GB used after three to four days on a 64 GB server, another more than 50 GB requested by a VM with no users, another 15 GB for a single user with the backend restarted every 12 hours. These are individual reports, not reproduced: a member of the Penpot team answers in the thread that they run high-traffic Docker installations without this symptom, and asks for a file and traces to diagnose. We therefore cannot promise a consumption figure. What we can say is how to avoid letting one service decide alone: the backend is a Java process, and without a container limit the Java virtual machine sizes its heap from the memory it sees, that is, the memory of the whole host. A per-service limit turns a silent drift into a controlled restart.
Sizing memory: limit, monitoring, scheduled restart
A contributor to thread #3299 reports adding mem_limit: 12g to every service of the Compose file to keep consumption bearable. On a 4 GB VPS, set lower values, for example mem_limit: 2g on penpot-backend and mem_limit: 1g on penpot-exporter, and leave the rest to PostgreSQL and the system. The backend launch script appends JVM_OPTS and JAVA_OPTS to the Java command line: you can pass -XX:MaxRAMPercentage=70 there, a standard JVM option, but the Penpot documentation describes no JVM tuning, so test it before production. Monitor with docker stats --no-stream, and if the backend drifts despite the limit, plan a nightly restart (docker compose -p penpot -f docker-compose.yaml restart penpot-backend): collaborators reconnect their websockets. Valkey is already capped at 128 MB in the upstream Compose file. If the limit is hit too often, that is the signal to choose a VPS plan with more RAM, not to raise the limit blindly.
Importing your files from Figma
Contrary to what you sometimes read, Penpot does not import a .fig file. The project maintains a Figma plugin, "Penpot Exporter", which converts a Figma file into an importable .zip archive. In Figma, open the file, then Resources, Plugins, search for "Penpot Exporter" and run the export. In Penpot, open the three-dot menu of a project or of drafts, choose "Import Penpot files" and select the .zip. According to the plugin README, these are carried over: basic shapes, vectors, frames and sections (boards in Penpot), groups and masks, texts, fills, strokes, shadows, components and instances, auto layouts, variants and colour and typography libraries. Not carried over: prototyping settings. Very large files remain a hard point, because the export walks every node under the Figma API limits, and some renderings may differ slightly. Import a test file first, compare components and fonts, then migrate the rest. Custom fonts are uploaded to Penpot.
Fitting Penpot into your Git workflow
Penpot does not store your designs as text files in a repository: the source of truth is the PostgreSQL database. Two exports, however, suit Git. The .penpot archive serves as a portable backup of a file. The SVG export of a board produces readable text that you can commit to Gitea, for example for the icons and illustrations of a design system. A pull request then shows the SVG diff, and your CI pipeline can detect an unexpected change. This approach remains a team convention: it assumes re-exporting after each change, which a script or a design review can remind you to do.
Penpot MCP: drive your designs from an AI assistant
The upstream Compose file includes a penpot-mcp service, enabled by the enable-mcp flag, and its reverse proxy must relay the /mcp/ws websocket in addition to /ws/notifications. An MCP-compatible client can then connect to your instance. Reserve this access for trusted accounts and serve it over HTTPS. Test on a draft file first, then generalise if the result suits you.
Troubleshooting: the errors actually reported
- "Something wrong has happened" banner or rejected
auth-tokencookie — reported on a server reached over HTTP from the local network. Answer from a Penpot team member: cookies aresecureby default, so frontend and backend must be served over HTTPS. Serve the instance behind a valid certificate and setPENPOT_PUBLIC_URIto the exact URL. - Internal error after a few clicks on sign-up or login — reported with
disable-secure-session-cookieson public HTTP access. Same remedy: HTTPS. The flag is not a production fix. WebSocket connection to 'wss://…/ws/notifications…' failed: Invalid frame header— reported right after login, with the interface freezing, behind Nginx. The answer given in the community is to relay/ws/notificationswith theUpgradeandConnectionheaders to port 9001. The thread does not confirm that this resolved the requester's case.net::ERR_CERT_AUTHORITY_INVALIDin thepenpot-exporterlogs — the export fails. The exporter loads the application throughPENPOT_PUBLIC_URI: with a self-signed or unrecognised certificate, it cannot validate the connection. Use a certificate from a recognised authority.- PDF export that times out without a clear message — reported on a manual installation where the exporter received
PENPOT_PUBLIC_URI=http://localhost. The exporter must be able to reach the public URL of the application, not an address local to its own container. - Cannot log in, no lead — first check
docker compose -p penpot -f docker-compose.yaml ps, then the logs of the service concerned, and the browser console (F12), which shows the exact Penpot version and the failing requests.