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

FeaturePenpotFigmaLunacyInkscape
LicenseMPL 2.0 (open source)ProprietaryProprietary (free)GPL
Self-hostable✅ Full self-hosting❌ SaaS only❌ Desktop only✅ Desktop only
Real-time collaboration✅ Built-in✅ Built-in❌ No❌ No
Output formatSVG, CSS, HTMLProprietaryProprietarySVG
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
PricingFree foreverFree tier + $15/editor/moFreeFree
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

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.

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 ps to find the correct backend container name — it might be penpot-penpot-backend-1 or penpot_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):

ComponentRAMCPUDisk
Frontend~100 MBLowMinimal
Backend~800 MB - 1.5 GBMediumMinimal
Exporter~300 MBLow (spikes during export)Minimal
PostgreSQL~200 MBLowGrows with projects
Valkey (Redis)~50 MBMinimalMinimal
Total~1.5 - 2.2 GB2+ cores recommended5-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.