Why Designers Are Moving Away from Figma’s Cloud
Figma changed collaborative design forever when it moved everything to the browser, but that convenience comes with a real cost: your design files, your assets, and your team’s entire creative output live on someone else’s servers. For freelancers working with NDAs, agencies handling sensitive brand work, or small studios watching subscription costs stack up, that arrangement stops making sense fast. Penpot offers a direct answer – an open-source, self-hosted design tool with a feature set that covers most of what Figma does, without the monthly bill or the data dependency.
Penpot is built on open web standards, meaning it uses SVG natively rather than a proprietary format. That matters because your design files stay portable and readable without needing a specific app to decode them.
This guide walks through spinning up a Penpot instance using Docker Compose, configuring it behind a reverse proxy, and getting your first workspace ready. You’ll need a Linux server – a VPS with at least 2GB of RAM works fine – Docker and Docker Compose installed, and a domain name pointed at your server if you want SSL. The whole setup takes under an hour.

Installing Penpot with Docker Compose
Penpot’s team maintains an official Docker Compose configuration that bundles everything you need: the frontend, the backend API, an Exporter service for rendering assets, and a PostgreSQL database. Start by pulling the official configuration file directly from Penpot’s GitHub repository. Create a working directory – something like /opt/penpot – and download the file there.
mkdir -p /opt/penpot && cd /opt/penpot
curl -o docker-compose.yaml https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml
Before running anything, open that file in a text editor and review the environment variables block under the penpot-backend service. The most important ones to set immediately are PENPOT_FLAGS, PENPOT_SECRET_KEY, and the email configuration. For PENPOT_SECRET_KEY, generate a long random string – you can use openssl rand -hex 32 in your terminal. By default, Penpot runs with the enable-registration flag active, which lets anyone with your URL create an account. If this is a private instance, add disable-registration to that flags variable and manage invites manually instead. For the email settings, Penpot needs an SMTP server to send verification emails and invites – plug in your provider’s credentials under the PENPOT_SMTP_* variables. If you’re testing locally and want to skip email entirely, set enable-smtp false in the flags and Penpot will log email content to the console instead.
Once the file is configured, start the stack:
docker compose up -d
Docker will pull the images on first run, which takes a few minutes depending on your connection. After that, all four containers – frontend, backend, exporter, and postgres – should show as running when you check with docker compose ps. By default, the frontend is accessible on port 9001. If you visit http://your-server-ip:9001, you should see the Penpot login screen. Do not leave it exposed on that port in production – the next step puts it behind a proper reverse proxy with SSL.

Putting Penpot Behind a Reverse Proxy
Running Penpot over plain HTTP is fine for a quick local test, but any real use demands HTTPS. Caddy is the simplest option here because it handles certificate provisioning automatically through Let’s Encrypt. If you already have a Caddy reverse proxy set up with SSL, adding Penpot is a single block in your Caddyfile. Point your domain or subdomain at the server, then add this:
design.yourdomain.com {
reverse_proxy localhost:9001
}
Caddy will handle the certificate automatically. After reloading Caddy with caddy reload, your Penpot instance will be live at https://design.yourdomain.com. One thing to confirm before moving on: go back to the docker-compose.yaml and make sure the PENPOT_PUBLIC_URI environment variable matches your actual domain exactly, including the https:// prefix. If this is wrong, things like file exports, shared links, and asset loading will break in non-obvious ways. Restart the stack after any environment variable change with docker compose down && docker compose up -d.
Nginx works equally well if that’s what you’re running. The proxy config is standard – set proxy_pass http://localhost:9001, increase client_max_body_size to at least 50MB to handle large file uploads, and enable WebSocket proxying with the standard Upgrade and Connection headers. Penpot’s real-time collaboration relies on WebSockets, so skipping those headers will make multiplayer editing fail silently.
Creating Your First Workspace and Managing Users
With the instance live over HTTPS, navigate to your domain and create the first admin account. If you left registration enabled, you’ll see a standard signup form. If you disabled it, you’ll need to invite yourself or use Penpot’s CLI to create the first user directly inside the backend container:
docker compose exec penpot-backend ./manage.py create-profile \
--email admin@yourdomain.com \
--password yourpassword \
--fullname "Your Name"
After logging in, Penpot drops you into the dashboard where you create teams and projects. Teams are how you organize collaboration – each team gets its own shared libraries, font uploads, and member permissions. Create a team first, then create a project inside it. Penpot’s interface will feel immediately familiar if you’ve spent time in Figma: the left panel handles layers and pages, the right panel covers design properties, and the toolbar at the top manages tools. Frames in Penpot behave like frames in Figma, and components work the same way with main components and instances. The grid layout tool, boolean operations, and prototype linking are all present. Where Penpot currently lags is plugin support – Figma’s plugin ecosystem is enormous, and Penpot’s is still maturing, so if your workflow depends on specific Figma plugins for things like icon imports or design tokens, check compatibility before committing.
Shared libraries deserve a specific callout because they work slightly differently. In Penpot, you publish a file as a shared library from within the editor under File > Add as Shared Library, and then any other file in the same team can link to it. Updates to the library push to linked files automatically, which is exactly how Figma handles it – the workflow will feel natural. For fonts, upload custom typefaces through the team settings panel rather than relying on Google Fonts integration; self-hosted means you control what loads, and keeping fonts local avoids external requests entirely.

What You Get – and What to Watch
A self-hosted Penpot instance gives you full control over your design data, zero per-seat licensing costs, and a tool that’s actively developed with a growing feature set – but the maintenance burden is real. Docker updates, database backups, and monitoring uptime are now your responsibility, not Figma’s. Set up a backup cron job for the PostgreSQL volume from day one, before you have files worth losing.





