Self-Hosting Penpot: Open Source Figma Alternative with Docker
When Adobe acquired Figma for $20 billion in 2022 (later abandoned), it sent a clear message: your design tools are only as independent as the company behind them. Penpot exists so that message doesn’t have to matter.
Penpot is a fully open-source design and prototyping platform licensed under MPL 2.0. It runs in the browser, supports real-time collaboration, outputs native SVG/CSS/HTML, and — crucially — can be self-hosted on your own infrastructure. No vendor lock-in, no per-seat pricing that scales into absurdity, no surprise acquisitions that change the terms overnight.
The 2.0 release brought CSS Grid Layout (a first for any design tool), a rebuilt component system with variants, and native design tokens. It’s not trying to be a pixel-perfect Figma clone — it’s building something that bridges design and development in ways proprietary tools can’t.
Penpot vs Figma vs Alternatives
| Feature | Penpot | Figma | Lunacy | Inkscape |
|---|---|---|---|---|
| License | MPL 2.0 (open source) | Proprietary | Proprietary (free) | GPL |
| Self-hostable | ✅ Full self-hosting | ❌ SaaS only | ❌ Desktop only | ✅ Desktop only |
| Real-time collaboration | ✅ Built-in | ✅ Built-in | ❌ No | ❌ No |
| Output format | SVG, CSS, HTML | Proprietary | Proprietary | SVG |
| Design tokens | ✅ Native support | ❌ Plugin required | ❌ No | ❌ No |
| CSS Grid Layout | ✅ Native | ❌ Auto Layout only | ❌ No | ❌ No |
| Prototyping | ✅ Interactive flows | ✅ Advanced | ✅ Basic | ❌ No |
| Components/variants | ✅ Full system | ✅ Full system | ✅ Basic | ❌ No |
| Plugin ecosystem | ✅ Growing | ✅ Massive | ❌ Limited | ✅ Extensions |
| Pricing | Free forever | Free tier + $15/editor/mo | Free | Free |
| RAM usage | ~1-2 GB (server) | N/A (SaaS) | ~500 MB | ~200 MB |
Penpot’s killer advantage is the open standards approach. Designs are stored as SVG, and the inspect panel gives developers real CSS and HTML — not approximations. For teams that value ownership and developer handoff, it’s the strongest option available.
Prerequisites
Before starting, make sure you have:
- A Linux server with at least 2 CPU cores and 4 GB RAM (8 GB recommended for teams)
- Docker and Docker Compose installed (installation guide)
- A domain name pointed at your server (e.g.,
design.yourdomain.com) - A reverse proxy for HTTPS (Caddy, Nginx, or Traefik)
- SMTP credentials for email notifications (optional but recommended for team use)
- ~10 GB disk space for the application, database, and assets
Step 1: Create the Project Directory
Start by creating a dedicated directory for your Penpot deployment:
mkdir -p ~/penpot && cd ~/penpot
Download the official Docker Compose file from the Penpot repository:
curl -o docker-compose.yaml https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml
This gives you the reference configuration with all available options documented as comments.
Step 2: Generate a Secret Key
Penpot requires a secret key for session management, invitations, and other cryptographic operations. Generate a secure one:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"
Copy the output — you’ll need it in the next step.
Step 3: Configure Environment Variables
Create a .env file to keep your configuration clean and separate from the compose file:
cat > .env << 'EOF'
# Pin a specific version for reproducible deployments
PENPOT_VERSION=2.4.3
EOF
Now edit the docker-compose.yaml file to set your configuration. The key sections to modify:
Secret Key
Find the x-secret-key anchor and replace the default value:
x-secret-key: &penpot-secret-key
PENPOT_SECRET_KEY: your-generated-secret-key-here
Public URI
Update the public URI to match your domain:
x-uri: &penpot-public-uri
PENPOT_PUBLIC_URI: https://design.yourdomain.com
Flags
For a production deployment with HTTPS, update the flags to enable secure cookies:
x-flags: &penpot-flags
PENPOT_FLAGS: enable-login-with-password enable-smtp enable-prepl-server enable-registration
Note that we removed disable-secure-session-cookies and disable-email-verification — these should only be used for local testing, never in production.
Database Credentials
Change the default database password:
# In the penpot-backend environment section:
PENPOT_DATABASE_PASSWORD: a-strong-database-password
# In the penpot-postgres environment section:
- POSTGRES_PASSWORD=a-strong-database-password
Step 4: Configure SMTP (Recommended)
For team use, you’ll want real email delivery instead of the built-in mailcatcher. Update the SMTP settings in the backend service:
PENPOT_SMTP_DEFAULT_FROM: [email protected]
PENPOT_SMTP_DEFAULT_REPLY_TO: [email protected]
PENPOT_SMTP_HOST: smtp.yourmailprovider.com
PENPOT_SMTP_PORT: 587
PENPOT_SMTP_USERNAME: your-smtp-username
PENPOT_SMTP_PASSWORD: your-smtp-password
PENPOT_SMTP_TLS: true
PENPOT_SMTP_SSL: false
Popular SMTP options for self-hosters include Mailgun, Amazon SES, Resend, or a self-hosted mail server. If you’re just testing, the included penpot-mailcatch service captures all emails at http://localhost:1080.
Step 5: Set Up a Reverse Proxy
Penpot needs HTTPS for production use — clipboard functionality, service workers, and secure cookies all require it. Here are configurations for the most popular reverse proxies.
Option A: Caddy (Recommended — Simplest)
If you’re running Caddy, add this to your Caddyfile:
design.yourdomain.com {
reverse_proxy localhost:9001
}
That’s it. Caddy handles HTTPS certificates automatically.
Option B: Nginx
server {
listen 80;
server_name design.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name design.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/design.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/design.yourdomain.com/privkey.pem;
client_max_body_size 350M;
location /ws/notifications {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://localhost:9001/ws/notifications;
}
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://localhost:9001/;
}
}
The WebSocket location block for /ws/notifications is critical — without it, real-time collaboration won’t work.
Option C: Traefik
The official docker-compose.yaml includes commented-out Traefik labels. Uncomment the Traefik service and the labels on penpot-frontend, replacing <DOMAIN_NAME> and <EMAIL_ADDRESS> with your values.
Step 6: Launch Penpot
Start all services:
docker compose -p penpot -f docker-compose.yaml up -d
Watch the logs to verify everything starts correctly:
docker compose -p penpot logs -f
You should see the backend connect to PostgreSQL, run any pending migrations, and begin listening. The frontend will start serving on port 9001 internally.
Once the logs settle, visit https://design.yourdomain.com and you should see the Penpot login screen.
Step 7: Create Your First User
If you enabled registration in the flags, you can sign up directly through the web interface. For private instances where you want to control access, disable registration and create users via the CLI:
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile
This interactive command will prompt for email and password. To skip the onboarding tutorial:
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile \
--skip-tutorial --skip-walkthrough
Note: The exact container name depends on your Docker version. Run
docker psto find the correct backend container name — it might bepenpot-penpot-backend-1orpenpot_penpot-backend_1.
Configuring Authentication Providers
Penpot supports several OAuth/OIDC providers for team authentication. Add the appropriate flags and environment variables to the backend service.
Google OAuth
PENPOT_FLAGS: ... enable-login-with-google
PENPOT_GOOGLE_CLIENT_ID: your-google-client-id
PENPOT_GOOGLE_CLIENT_SECRET: your-google-client-secret
GitHub OAuth
PENPOT_FLAGS: ... enable-login-with-github
PENPOT_GITHUB_CLIENT_ID: your-github-client-id
PENPOT_GITHUB_CLIENT_SECRET: your-github-client-secret
Generic OIDC (Authentik, Authelia, Keycloak)
PENPOT_FLAGS: ... enable-login-with-oidc
PENPOT_OIDC_CLIENT_ID: your-client-id
PENPOT_OIDC_CLIENT_SECRET: your-client-secret
PENPOT_OIDC_BASE_URI: https://auth.yourdomain.com/application/o/penpot/
PENPOT_OIDC_AUTH_URI: https://auth.yourdomain.com/application/o/authorize/
PENPOT_OIDC_TOKEN_URI: https://auth.yourdomain.com/application/o/token/
PENPOT_OIDC_USER_URI: https://auth.yourdomain.com/application/o/userinfo/
This integrates cleanly with self-hosted SSO solutions like Authentik or Authelia.
Backup Strategy
Penpot stores data in two Docker volumes: one for PostgreSQL and one for uploaded assets. Both need regular backups.
Database Backup
docker exec penpot-penpot-postgres-1 \
pg_dump -U penpot penpot | gzip > penpot-db-$(date +%Y%m%d).sql.gz
Assets Backup
docker run --rm \
-v penpot_penpot_assets:/data \
-v $(pwd)/backups:/backup \
alpine tar czf /backup/penpot-assets-$(date +%Y%m%d).tar.gz -C /data .
Automated Backup Script
#!/bin/bash
BACKUP_DIR=~/penpot/backups
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d-%H%M)
# Database
docker exec penpot-penpot-postgres-1 \
pg_dump -U penpot penpot | gzip > "$BACKUP_DIR/db-$DATE.sql.gz"
# Assets
docker run --rm \
-v penpot_penpot_assets:/data \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/assets-$DATE.tar.gz" -C /data .
# Retain last 7 days
find "$BACKUP_DIR" -name "*.gz" -mtime +7 -delete
echo "Backup completed: $DATE"
Add this to cron for daily automated backups:
crontab -e
# Add: 0 3 * * * /home/user/penpot/backup.sh
Updating Penpot
Pull the latest images and recreate containers:
cd ~/penpot
docker compose -f docker-compose.yaml pull
docker compose -p penpot -f docker-compose.yaml up -d
For version-pinned deployments, update PENPOT_VERSION in your .env file, then run the same commands. Penpot recommends incremental updates rather than jumping multiple major versions.
Troubleshooting
WebSocket Connection Errors
If real-time collaboration doesn’t work, your reverse proxy likely isn’t forwarding WebSocket connections. Ensure the /ws/notifications path has proper upgrade headers configured. With Caddy this works automatically; Nginx and Traefik need explicit WebSocket configuration.
High Memory Usage
Penpot’s backend (JVM-based) can be memory-hungry. If you’re running on a constrained server, set JVM memory limits:
# In penpot-backend environment
JAVA_OPTS: -Xmx1g -Xms512m
“Clipboard Not Working” Errors
This almost always means you’re accessing Penpot over HTTP instead of HTTPS. Browser clipboard APIs require a secure context. Set up HTTPS through your reverse proxy and remove the disable-secure-session-cookies flag.
Container Name Mismatch
If CLI commands fail with “no such container,” run docker ps to check the actual container names. Docker Compose versions handle naming slightly differently — you might see hyphens or underscores as separators.
Exporter Not Working
The exporter service needs to communicate with the frontend over the internal Docker network. If PDF/image exports fail, verify the exporter’s PENPOT_PUBLIC_URI is set to http://penpot-frontend:8080 (internal), not your external domain.
Resource Usage
Typical resource consumption for a small team (5-10 users):
| Component | RAM | CPU | Disk |
|---|---|---|---|
| Frontend | ~100 MB | Low | Minimal |
| Backend | ~800 MB - 1.5 GB | Medium | Minimal |
| Exporter | ~300 MB | Low (spikes during export) | Minimal |
| PostgreSQL | ~200 MB | Low | Grows with projects |
| Valkey (Redis) | ~50 MB | Minimal | Minimal |
| Total | ~1.5 - 2.2 GB | 2+ cores recommended | 5-10 GB base |
For larger teams or heavy asset use, budget 4-8 GB RAM and faster storage.
Wrapping Up
Penpot gives you something no other design tool does: a production-grade, collaborative design platform that you actually own. The 2.0 release with CSS Grid, design tokens, and a rebuilt component system closed the gap with proprietary tools significantly.
Is it Figma? No — and it’s not trying to be. Figma has years of polish, a massive plugin ecosystem, and a community that’s hard to replicate. But Penpot offers something Figma never will: independence. Your designs live on your server in open formats. No subscription changes, no acquisition surprises, no feature paywalls.
For teams that value data sovereignty, open standards, and the ability to integrate design tooling into their own infrastructure, Penpot is the strongest option available in 2026.