Design tools are the last thing most teams manage to bring in-house, because the files are usually hostage to a format. Penpot removes that objection, and the deployment is a Compose file plus two environment variables that people get wrong. Both failures present as an application that loads and then does nothing, so it is worth knowing them before you start.
Penpot is the one open-source design tool that a team can actually move to, because files are open standards rather than a proprietary format and the whole thing runs on your own server. The deployment is Docker Compose and mostly undramatic, with two configuration details that decide whether it works at all.
The Services in the Stack
The Compose file brings up five containers and each has a job worth knowing before something misbehaves.
frontend serves the static application and proxies to the backend. backend holds the API and the business logic. exporter runs a headless browser to render PNG, SVG and PDF exports, which is why it is the memory-hungry one. postgres stores everything structural. redis carries the real-time collaboration messages between users editing the same board.
Assets, meaning uploaded images and fonts, go to a filesystem volume by default and can go to S3-compatible object storage instead. That choice matters for backups, since a database dump without the assets restores designs with missing images.
Sizing, and the Exporter
| Scale | vCPU / RAM / SSD | Monthly on per-resource pricing |
|---|---|---|
| Small team, up to 5 designers | 2 / 4 GB / 40 GB | $9.34 |
| Team of 10-20 | 4 / 8 GB / 80 GB | $18.68 |
| Design org, heavy exports | 8 / 16 GB / 200 GB | $37.76 |
At MassiveGRID's per-resource rates of $2.87 per CPU core, $0.80 per GB of RAM and $0.01 per GB of SSD per month, the middle row is $11.48 plus $6.40 plus $0.80, with 20% off on annual billing.
The exporter is the component that changes the sizing. It launches a headless Chromium per export job, and a team exporting a large document set concurrently will consume more memory than everything else combined. If exports fail while the editor works fine, the exporter was killed for memory, not misconfigured.
Deploy It
sudo apt update && sudo apt install -y docker.io docker-compose-plugin
sudo mkdir -p /opt/penpot && cd /opt/penpot
curl -o docker-compose.yaml https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml
cp .env.example .env 2>/dev/null || true
sudo docker compose up -d
sudo docker compose logs -f penpot-backend
Watch those logs on first start. The backend runs its database migrations at startup and will exit if it cannot reach Postgres, which on a first deploy usually means the database container is still initialising and a second up resolves it.
The Two Settings That Decide Everything
PENPOT_PUBLIC_URI must be the exact external URL, scheme included, that users will open. Penpot builds asset links and websocket URLs from it. Set it to http://localhost:9001 and then serve the app at https://design.example.com and you get an application that loads, shows a blank canvas, and fails silently because the browser is trying to open a websocket to localhost.
PENPOT_FLAGS is a space-separated list that switches features on and off, and its defaults are not what a production install wants. Two in particular:
PENPOT_PUBLIC_URI=https://design.example.com
PENPOT_FLAGS=enable-login-with-password disable-registration enable-smtp
PENPOT_TELEMETRY_ENABLED=false
disable-registration is the one to set on day one. A Penpot instance reachable from the internet with open registration collects accounts you did not create. Invite users instead, which needs SMTP working.
Reverse Proxy and Websockets
Collaboration runs over a websocket, so a proxy that does not upgrade the connection gives you an editor where other people's cursors never appear and changes do not sync. It looks like a Penpot bug and it is a proxy configuration.
server {
listen 443 ssl http2;
server_name design.example.com;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:9001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
}
Raise client_max_body_size, because design files with embedded images exceed the 1 MB default immediately and the failure presents as an import that stops without an error. The long proxy_read_timeout keeps the websocket from being cut mid-session.
Email Is How People Get In
With registration disabled, invitations are the only route to an account, and password resets are the only route back in. Both are email. An instance with no working SMTP is an instance you will eventually be locked out of.
Use an authenticated relay rather than sending directly from the server, and set SPF, DKIM and DMARC on the sending domain so invitations do not land in spam. Then test it by inviting yourself at an external address before inviting the team.
Backups That Include the Assets
docker compose exec -T penpot-postgres \
pg_dump -U penpot penpot | gzip > /backup/penpot-$(date +%F).sql.gz
tar czf /backup/penpot-assets-$(date +%F).tar.gz /opt/penpot/assets
Both lines, always, and restore both in a rehearsal. The database holds the document structure and the asset store holds the images referenced from it, so either alone gives you a partial restore that looks like corruption.
Keep the copies off the server. Backup services use block-level incremental backups with AES-256 encryption at $0.01 per GB, and a design team's history is the kind of thing nobody can recreate. For the file-format question, Penpot's own .penpot export is a useful second line, since it can be re-imported into any instance.
What You Give Up, and What You Gain
Against the hosted commercial tools, you give up plugin ecosystems, some polish in prototyping, and the fact that everyone already knows the other tool. You gain designs that stay on your infrastructure, files in open formats that no vendor can hold, no per-seat cost that grows with the team, and the ability to place the instance in a jurisdiction you chose.
For teams already self-hosting the rest of their stack, that trade is usually easy. Our guide to self-hosting Immich covers a similar calculation for photo storage, and sizing self-hosted applications covers running several of these on one server.
Infrastructure for a Collaborative Tool
Real-time collaboration is unforgiving about latency and about the server disappearing mid-session, so both placement and resilience matter more than for a tool people use alone.
MassiveGRID runs Proxmox high-availability clustering with automatic failover over Ceph storage replicating every block three times across independent NVMe drives, behind a 100% uptime SLA. A Linux VPS at the rates above covers most teams, and instances can be ordered across a partner footprint of more than 700 datacenters in 85 metros, 30 countries and six continents, with auto-provisioning in New York, London, Frankfurt and Singapore, so a distributed design team can sit near the instance rather than across an ocean from it.