Why Designers Are Moving Away From Figma
Figma’s acquisition by Adobe in 2022 – later abandoned under regulatory pressure – left a lot of design teams asking a question they hadn’t seriously considered before: what happens when the tool you depend on gets pulled out from under you? The answer, for a growing number of teams, has been to stop asking and start self-hosting. Penpot is the open-source design and prototyping tool that has absorbed a significant portion of that anxiety, offering a browser-based interface that will feel familiar to anyone who has spent time in Figma.
Penpot runs entirely in your own infrastructure. Your design files, your assets, your team’s work – none of it touches a third-party server unless you choose otherwise. For agencies handling client NDAs, startups with strict data residency requirements, or developers who simply prefer owning their stack, that distinction matters more than any feature comparison chart. This guide walks through a complete self-hosted Penpot setup using Docker, including reverse proxy configuration and first-run setup.

What You Need Before You Start
Penpot’s official deployment method uses Docker Compose, which keeps the setup relatively contained. You will need a Linux server – a VPS or a local machine running Ubuntu 22.04 or Debian 12 works well – with at least 2 GB of RAM, though 4 GB is more comfortable once multiple users are active. Docker and Docker Compose must be installed. If you are planning to expose Penpot over the internet rather than keeping it on a private network, you will also need a domain name pointing to your server and a reverse proxy to handle SSL termination. Nginx and Caddy both work well here.
Port 3449 is Penpot’s default frontend port, and 6060 handles the backend API. If you run a firewall – and you should – make sure those ports are accessible from your proxy but not exposed directly to the public internet. For teams already running self-hosted infrastructure, this setup slots in alongside other services without much friction. If you are new to self-hosting and want a reference point for monitoring your server once Penpot is running, Beszel is a lightweight option worth looking at.
Installing Penpot with Docker Compose
Start by pulling Penpot’s official Docker Compose configuration. The Penpot team maintains a repository on GitHub under the penpot/penpot namespace, and the compose file there is the recommended starting point. Run git clone https://github.com/penpot/penpot.git and navigate into the cloned directory. You will find a docker folder containing a docker-compose.yaml file along with an environment configuration file named config.env. Open config.env before running anything – this file controls most of what matters.
Inside config.env, the most important variable to set immediately is PENPOT_PUBLIC_URI. Set this to the full URL where Penpot will be accessible – for example, https://design.yourdomain.com. If this is left as localhost or set incorrectly, assets and websocket connections will break in ways that are annoying to debug after the fact. Also look at PENPOT_FLAGS: by default, email verification is enabled, which requires a working SMTP configuration. If you want to skip email verification during initial testing, add disable-email-verification to that flags list.
Email configuration lives under the SMTP section of config.env. Penpot needs to send registration and password reset emails, so unless you have disabled verification, fill in your SMTP host, port, username, and password. Most teams use a transactional email service here – the credentials go in as plain environment variables. Once that is done, bring everything up with docker compose -f docker/docker-compose.yaml up -d. The initial pull will take a few minutes as it downloads the frontend, backend, exporter, and PostgreSQL containers.
After the containers start, check that all services are healthy with docker compose ps. You are looking for the penpot-frontend, penpot-backend, penpot-exporter, and penpot-postgres containers to all show a running state. If the backend container is restarting repeatedly, the most common cause is a misconfigured PENPOT_PUBLIC_URI or a database connection issue – check logs with docker compose logs penpot-backend to see the specific error.

Setting Up the Reverse Proxy
If you are using Nginx, create a new server block that proxies traffic to Penpot’s frontend on port 3449. The backend API runs separately on port 6060, and Penpot’s frontend internally routes API calls through /api – so your proxy needs to forward requests under that path to the backend container rather than the frontend. A typical Nginx config will have a primary location / block pointing to http://localhost:3449 and a secondary location /api block pointing to http://localhost:6060. Websocket support is required, so include the standard proxy_set_header Upgrade and Connection “upgrade” headers in your frontend location block.
For SSL, Certbot handles the certificate provisioning cleanly with the certbot –nginx command once your DNS records have propagated. Caddy users can skip most of this – point Caddy at the same upstream ports with a simple reverse proxy directive, and it will handle HTTPS automatically. Either way, once the proxy is in place and HTTPS is confirmed working, open the URL in a browser. You should see the Penpot login screen.
Creating Your First Account and Configuring the Instance
The first account you register on a fresh Penpot instance becomes the admin. Navigate to your configured URL, click the registration link, and create that account using a real email address you have access to – or one you have whitelisted if you disabled email verification. After logging in, you will land on the Penpot dashboard, which shows your drafts and any shared team projects. The interface closely mirrors Figma’s project organization, with a left sidebar for navigation and a central grid for files.
Penpot does not have a built-in admin panel in the traditional sense – user management is handled through the command line rather than a GUI. To invite additional users, you can either enable open registration in config.env by setting PENPOT_ALLOW_DEMO_USERS and the registration flags appropriately, or manage users directly via the backend container’s management commands. Run docker exec -it penpot-backend ./manage.py –help to see available commands, including user creation and role assignment. This is a deliberate design choice rather than an oversight – it keeps the attack surface smaller for teams that want tight control over who can access the instance.
Font management in Penpot works differently from Figma’s Google Fonts integration. Custom fonts are uploaded per-team directly through the UI under the team settings panel. System fonts available to the server are also accessible, but Google Fonts as a live synced library is not a default feature in the self-hosted version. Teams migrating from Figma with established type systems will want to upload their fonts early, before designers start working, to avoid placeholder substitutions in existing files.

Migrating Files and Getting the Team Up to Speed
Penpot does not natively import Figma files, and there is no official bridge tool that preserves every detail of a complex component library. The practical workflow most teams use is to export individual frames or components from Figma as SVG files and import them into Penpot, then rebuild component structures natively. It is not instant, but Penpot’s component and design token system is capable enough that a rebuilt library will behave consistently across the platform – which is worth more than a fragile auto-imported mess.
For teams starting fresh rather than migrating, the onboarding is straightforward. Penpot’s vector editing tools, auto-layout system, and prototype linking all work in the browser with no plugin required. The exporter container handles asset downloads, generating PNGs, SVGs, and PDFs from designs without any additional configuration beyond what is already running. One thing worth testing before you announce the instance to the team: run through a full export cycle on a multi-frame prototype to confirm the exporter container has the right permissions and is writing to the shared storage volume correctly. That is where silent failures tend to hide.





