Penpot mit Docker installieren: Open-Source-Designtool als Figma-Alternative selbst hosten
Penpot ist die kostenlose Open-Source-Alternative zu Figma – SVG-basiert, kollaborativ, selbst hostbar per Docker Compose ohne Lizenzkosten. Diese Anleitung zeigt den kompletten Weg von der compose.yaml bis zum ersten Login.

Penpot ist eine quelloffene Design- und Prototyping-Plattform auf SVG-Basis, die kollaboratives UI/UX-Design, Prototyping und Code-Inspektion (CSS und HTML direkt aus dem Design heraus) in einem einzigen Werkzeug vereint. Für DACH-Agenturen und Webentwickler ist das der entscheidende Punkt: Figma-Teamlizenzen kosten ab 15 EUR pro Nutzer und Monat – Penpot läuft AGPL-lizenziert, ohne Nutzerlimits, ohne Lizenzkosten, auf deiner eigenen Infrastruktur. Mit 49.500+ GitHub-Stars und einem verifizierten Publisher-Status auf Docker Hub ist das Projekt alles andere als ein Experiment. Version 2.16 bringt zusätzlich einen integrierten MCP-Server, der KI-Assistenten wie Claude oder Cursor direkten Zugriff auf Designs ermöglicht. Diese Anleitung richtet sich an Linux-Admins, Selfhoster und KMU-Teams, die Penpot auf einem beliebigen Linux-Host – ob Root-Server, VPS oder NAS mit Docker-Unterstützung – produktionsreif aufsetzen wollen.
Voraussetzungen
- Docker Engine >= 20.10 und Docker Compose Plugin v2 (Befehl:
docker composeohne Bindestrich) – falls noch nicht installiert, siehe Docker und Docker Compose auf Linux installieren - Linux-Host, VM oder NAS mit Docker-Unterstützung (x86_64 oder arm64 – alle Penpot-Images unterstützen beide Architekturen nativ)
- Mindestens 2 GB RAM, empfohlen 4 GB für den Betrieb mit mehreren Nutzern
- Mindestens 10 GB freier Festplattenplatz (Images ca. 1,5 GB, Datenbank und Assets wachsen mit der Zeit)
- Internetverbindung für den initialen Image-Pull
- python3 auf dem Host (für die Secret-Key-Generierung)
- Für den Produktionsbetrieb: Domain mit DNS-Eintrag und ein Reverse Proxy für HTTPS – siehe Traefik als Docker-Reverse-Proxy mit automatischem HTTPS oder Caddy als Reverse Proxy
Penpot-Dienste und Eckdaten
| Dienst | Image | Funktion | Port (extern) |
|---|---|---|---|
| penpot-frontend | penpotapp/frontend:2.16 | Nginx + SPA (Web-UI) | 9001 → 8080 |
| penpot-backend | penpotapp/backend:2.16 | Clojure-App-Server | nur intern |
| penpot-mcp | penpotapp/mcp:2.16 | MCP-Server für KI-Workflows (neu ab 2.16) | nur intern |
| penpot-exporter | penpotapp/exporter:2.16 | Node.js Export-Service | nur intern |
| penpot-postgres | postgres:15 | Relationale Datenbank | nur intern |
| penpot-valkey | valkey/valkey:8.1 | In-Memory-Store (Redis-Fork) | nur intern |
| penpot-mailcatch | sj26/mailcatcher:latest | SMTP-Trap für Entwicklung | 1080 (WebUI) |
| Volume | Inhalt | Backup-Priorität |
|---|---|---|
| penpot_assets | Hochgeladene Bilder, Fonts, Exporte | Hoch |
| penpot_postgres_v15 | Alle Designdaten (PostgreSQL) | Kritisch |
Schritt 1: Projektordner anlegen und Secret Key generieren
Lege einen dedizierten Ordner für den Penpot-Stack an. Alle Konfigurationsdateien liegen dort:
mkdir -p /opt/penpot
cd /opt/penpotGeneriere jetzt den PENPOT_SECRET_KEY. Dieser Schlüssel sichert alle Nutzersessions und Einladungslinks. Wenn er fehlt oder sich ändert, werden bei jedem Container-Neustart alle aktiven Sessions ungültig – das nervt im Team-Betrieb erheblich:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"Die Ausgabe sieht ungefähr so aus (dein Wert ist anders):
xK8mZ2q...v9Lp4nR # 88 Zeichen, Base64-URL-sicherKopiere diesen Wert – du brauchst ihn im nächsten Schritt für die .env-Datei.
Verifizieren: Stelle sicher, dass der Ordner existiert: ls -la /opt/penpot/ sollte ein leeres Verzeichnis zeigen. Der generierte Schlüssel sollte mindestens 80 Zeichen lang sein.
Schritt 2: .env-Datei mit Konfiguration anlegen
Die .env-Datei enthält alle anpassbaren Parameter. Für den lokalen Betrieb genügen die folgenden Einstellungen. Für den Produktionsbetrieb muss PENPOT_PUBLIC_URI auf die tatsächliche öffentliche URL zeigen (inkl. https://):
# Penpot – Konfiguration
# Generiert mit: python3 -c "import secrets; print(secrets.token_urlsafe(64))"
PENPOT_SECRET_KEY=HIER_DEINEN_GENERIERTEN_SCHLUESSEL_EINTRAGEN
# Öffentliche URL – muss exakt der Adresse entsprechen, unter der Nutzer Penpot erreichen.
# Für lokale Tests: http://localhost:9001
# Für Produktion: https://penpot.deine-domain.de
PENPOT_PUBLIC_URI=http://localhost:9001
# Penpot-Version (leer lassen = latest; konkrete Version empfohlen für Reproduzierbarkeit)
PENPOT_VERSION=2.16
# Datenbankpasswort – unbedingt in Produktion ändern!
PENPOT_DATABASE_PASSWORD=penpot_sicher_aendernSetze die Dateiberechtigungen so, dass nur root die Datei lesen kann:
chmod 600 /opt/penpot/.envVerifizieren: stat /opt/penpot/.env sollte Access: 0600 anzeigen. Stelle sicher, dass PENPOT_SECRET_KEY nicht mehr den Platzhalter-Text enthält.
Schritt 3: compose.yaml anlegen
Die folgende compose.yaml verwendet YAML-Anker (&penpot-flags, &penpot-secret-key etc.) um Redundanz zu vermeiden – ein Muster aus dem offiziellen Upstream-File, das Konfigurationsfehler durch doppelte Einträge verhindert. Das Datenbankpasswort wird aus der .env gelesen:
x-flags: &penpot-flags
PENPOT_FLAGS: >-
disable-email-verification
enable-smtp
enable-prepl-server
disable-secure-session-cookies
enable-mcp
x-uri: &penpot-public-uri
PENPOT_PUBLIC_URI: ${PENPOT_PUBLIC_URI:-http://localhost:9001}
x-body-size: &penpot-http-body-size
PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 367001600
PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE: 367001600
x-secret-key: &penpot-secret-key
PENPOT_SECRET_KEY: ${PENPOT_SECRET_KEY}
networks:
penpot:
volumes:
penpot_postgres_v15:
penpot_assets:
services:
penpot-frontend:
image: "penpotapp/frontend:${PENPOT_VERSION:-2.16}"
restart: always
ports:
- "9001:8080"
volumes:
- penpot_assets:/opt/data/assets
depends_on:
- penpot-backend
- penpot-exporter
- penpot-mcp
networks:
- penpot
environment:
<<: [*penpot-flags, *penpot-http-body-size, *penpot-public-uri]
penpot-backend:
image: "penpotapp/backend:${PENPOT_VERSION:-2.16}"
restart: always
volumes:
- penpot_assets:/opt/data/assets
depends_on:
penpot-postgres:
condition: service_healthy
penpot-valkey:
condition: service_healthy
networks:
- penpot
environment:
<<: [*penpot-flags, *penpot-public-uri, *penpot-http-body-size, *penpot-secret-key]
PENPOT_DATABASE_URI: postgresql://penpot-postgres/penpot
PENPOT_DATABASE_USERNAME: penpot
PENPOT_DATABASE_PASSWORD: ${PENPOT_DATABASE_PASSWORD:-penpot}
PENPOT_REDIS_URI: redis://penpot-valkey/0
PENPOT_OBJECTS_STORAGE_BACKEND: fs
PENPOT_OBJECTS_STORAGE_FS_DIRECTORY: /opt/data/assets
PENPOT_TELEMETRY_ENABLED: "true"
PENPOT_TELEMETRY_REFERER: compose
PENPOT_SMTP_DEFAULT_FROM: no-reply@example.com
PENPOT_SMTP_DEFAULT_REPLY_TO: no-reply@example.com
PENPOT_SMTP_HOST: penpot-mailcatch
PENPOT_SMTP_PORT: 1025
PENPOT_SMTP_USERNAME:
PENPOT_SMTP_PASSWORD:
PENPOT_SMTP_TLS: "false"
PENPOT_SMTP_SSL: "false"
penpot-mcp:
image: "penpotapp/mcp:${PENPOT_VERSION:-2.16}"
restart: always
networks:
- penpot
penpot-exporter:
image: "penpotapp/exporter:${PENPOT_VERSION:-2.16}"
restart: always
depends_on:
penpot-valkey:
condition: service_healthy
networks:
- penpot
environment:
<<: [*penpot-secret-key]
PENPOT_PUBLIC_URI: http://penpot-frontend:8080
PENPOT_REDIS_URI: redis://penpot-valkey/0
penpot-postgres:
image: "postgres:15"
restart: always
stop_signal: SIGINT
healthcheck:
test: ["CMD-SHELL", "pg_isready -U penpot"]
interval: 2s
timeout: 10s
retries: 5
start_period: 2s
volumes:
- penpot_postgres_v15:/var/lib/postgresql/data
networks:
- penpot
environment:
- POSTGRES_INITDB_ARGS=--data-checksums
- POSTGRES_DB=penpot
- POSTGRES_USER=penpot
- POSTGRES_PASSWORD=${PENPOT_DATABASE_PASSWORD:-penpot}
penpot-valkey:
image: valkey/valkey:8.1
restart: always
healthcheck:
test: ["CMD-SHELL", "valkey-cli ping | grep PONG"]
interval: 1s
timeout: 3s
retries: 5
start_period: 3s
networks:
- penpot
environment:
- VALKEY_EXTRA_FLAGS=--maxmemory 128mb --maxmemory-policy volatile-lfu
penpot-mailcatch:
image: sj26/mailcatcher:latest
restart: always
expose:
- "1025"
ports:
- "1080:1080"
networks:
- penpotEin wichtiger Hinweis zu Valkey: Ältere Community-Anleitungen verwenden noch redis:alpine oder redis:7. Das offizielle Compose-File nutzt seit Penpot 2.x valkey/valkey:8.1 – einen Redis-kompatiblen Fork. Verwende nur das offizielle File als Referenz.
Verifizieren: Prüfe die YAML-Syntax vor dem ersten Start:
docker compose -f /opt/penpot/compose.yaml config --quiet && echo "YAML OK"Erwartete Ausgabe: YAML OK ohne Fehlermeldungen.
Schritt 4: Stack starten und Gesundheit prüfen
Starte alle Dienste aus dem Projektordner heraus:
cd /opt/penpot
docker compose up -dDocker lädt beim ersten Mal alle Images herunter (ca. 1,5 GB gesamt). Danach starten die Container. PostgreSQL und Valkey verfügen über Healthchecks – Backend und Exporter warten automatisch, bis die Datenbanken bereit sind.
Status aller Dienste prüfen:
docker compose psErwartete Ausgabe nach etwa 30–60 Sekunden:
NAME STATUS PORTS
penpot-penpot-backend-1 Up
penpot-penpot-exporter-1 Up
penpot-penpot-frontend-1 Up 0.0.0.0:9001->8080/tcp
penpot-penpot-mailcatch-1 Up 0.0.0.0:1080->1080/tcp
penpot-penpot-mcp-1 Up
penpot-penpot-postgres-1 Up (healthy)
penpot-penpot-valkey-1 Up (healthy)Prüfe die Backend-Logs auf Startfehler (besonders hilfreich bei Datenbankproblemen):
docker compose logs penpot-backend --tail 30Die Ausgabe sollte Zeilen wie Starting Penpot backend und Server started enthalten – ohne ERROR-Einträge.
Verifizieren:
curl -I http://localhost:9001Erwartete Antwort: HTTP/1.1 200 OK oder HTTP/1.1 302 Found. Jede andere Antwort (Connection refused, 502) deutet auf einen noch nicht gestarteten Frontend-Container hin – warte weitere 30 Sekunden und wiederhole.
Schritt 5: Ersten Nutzer registrieren und Login testen
Öffne Penpot im Browser unter http://localhost:9001 (bzw. der konfigurierten PENPOT_PUBLIC_URI). Du siehst die Registrierungsseite. Lege hier den ersten Nutzer an – dieser wird automatisch der erste Teamowner.
Da disable-email-verification aktiv ist, entfällt die E-Mail-Bestätigung. Du kannst dich nach der Registrierung direkt einloggen.
Nach dem Login empfiehlt sich, die Registrierung für neue Nutzer zu sperren. Füge dazu disable-registration zu PENPOT_FLAGS in der compose.yaml hinzu und starte die betroffenen Dienste neu:
docker compose up -d --force-recreate penpot-backend penpot-frontendWeitere Nutzer können dann per CLI angelegt werden (das Flag enable-prepl-server ist bereits gesetzt):
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profileVerifizieren: Nach dem Login siehst du das leere Penpot-Dashboard. Lege ein Test-Projekt an und öffne eine leere Designdatei – der Editor muss sich fehlerfrei laden. Prüfe zusätzlich den Mailcatcher unter http://localhost:1080: Dort sollte die Willkommens-E-Mail erscheinen, die Penpot nach der Registrierung versendet.
Schritt 6: HTTPS und Reverse Proxy für den Produktionsbetrieb
Für den Einsatz im Team ist HTTPS nicht optional. Ohne HTTPS verweigert der Browser die Clipboard-API (Kopieren und Einfügen von Design-Elementen), die Webcrypto-API und sichere Cookies. Das Flag disable-secure-session-cookies löst nur das Cookie-Problem – die Browser-API-Einschränkungen bleiben.
Richte einen Reverse Proxy deiner Wahl vor Port 9001 ein:
- Traefik: im offiziellen Penpot-Compose-File als auskommentierter Block enthalten, automatisches Let's-Encrypt-Zertifikat – ideal wenn du mehrere Docker-Dienste veröffentlichst. Anleitung: Traefik als Docker-Reverse-Proxy mit automatischem HTTPS
- Caddy: minimale Konfiguration, automatisches HTTPS ohne separate Zertifikatsverwaltung
- Nginx Proxy Manager: grafische Oberfläche, gut für NAS-Umgebungen
Nachdem HTTPS eingerichtet ist, aktualisiere PENPOT_PUBLIC_URI in der .env auf die HTTPS-URL und entferne das Flag disable-secure-session-cookies aus PENPOT_FLAGS:
PENPOT_PUBLIC_URI=https://penpot.deine-domain.de
cd /opt/penpot && docker compose up -dVerifizieren:
curl -I https://penpot.deine-domain.deErwartete Antwort: HTTP/2 200 oder HTTP/2 302. Teste im Browser, ob das Kopieren und Einfügen von Design-Elementen funktioniert.
Schritt 7: Updates einspielen und Backups einrichten
Um Penpot auf eine neue Version zu aktualisieren, passe PENPOT_VERSION in der .env an und führe dann aus:
cd /opt/penpot
docker compose pull
docker compose up -dWichtig: Aktualisiere immer schrittweise. Überspringe keine großen Versionssprünge – der Upgrade von 1.x auf 2.x löst eine automatische Datenmigration aus, die bei großen Datenmengen mehrere Minuten dauern kann. Beobachte dabei die Logs:
docker compose logs -f penpot-backendPrüfe vor einem Upgrade außerdem den Volume-Namen in deiner bestehenden Installation: Das Volume heißt penpot_postgres_v15 (mit Versionssuffix). Wer von einer älteren Installation ohne Suffix (penpot_postgres) migriert, muss das Volume manuell umbenennen – sonst erstellt Docker ein leeres neues Volume und alle Designdaten sind verloren.
Für Backups müssen zwei Volumes gesichert werden:
# Assets-Volume sichern
docker run --rm \
-v penpot_assets:/data:ro \
-v /backup/penpot:/backup \
alpine tar czf /backup/penpot_assets_$(date +%Y%m%d).tar.gz -C /data .
# PostgreSQL-Datenbank-Dump
docker exec penpot-penpot-postgres-1 \
pg_dump -U penpot penpot | gzip > /backup/penpot_db_$(date +%Y%m%d).sql.gzFür automatisierte Backups und eine vollständige Backup-Strategie siehe MySQL & PostgreSQL Backup automatisieren mit cron und rclone.
Verifizieren:
docker compose psNach dem Update sollten alle sieben Container wieder den Status Up (Datenbank-Container Up (healthy)) zeigen. Logge dich im Browser ein und prüfe, ob bestehende Projekte und Designs weiterhin zugänglich sind.
Troubleshooting / Typische Fehler
- Alle Nutzer werden nach Neustart ausgeloggt:
PENPOT_SECRET_KEYist nicht gesetzt oder ändert sich bei jedem Start (z. B. weil der Platzhalter-Wert belassen wurde). Lösung: Starken Schlüssel generieren (python3 -c "import secrets; print(secrets.token_urlsafe(64))"), fest in.enveintragen, Stack neu starten. - Einladungslinks und OAuth-Callbacks schlagen fehl:
PENPOT_PUBLIC_URIstimmt nicht mit der tatsächlichen Zugriffs-URL überein. Nach einem Reverse-Proxy-Umbau oder Domain-Wechsel muss der Wert in.envaktualisiert und der Stack neu gestartet werden. - Clipboard-API funktioniert nicht (Kopieren/Einfügen im Editor): Penpot läuft über HTTP ohne HTTPS. Nur unter HTTPS erlaubt der Browser die Clipboard-API. Richte einen Reverse Proxy mit TLS ein.
- Backend startet nicht, Logs zeigen „database does not exist" oder „connection refused": Der PostgreSQL-Container ist noch nicht
healthy. In der Regel wartet das Backend durch dencondition: service_healthy-Check automatisch. Bei anhaltenden Problemen:docker compose logs penpot-postgresprüfen. - penpot-mcp fehlt im Stack / Frontend lädt nicht korrekt: Die verwendete
compose.yamlbasiert auf einer alten Version vor 2.16. Derpenpot-frontend-Dienst hat eindepends_onaufpenpot-mcp. Lösung: Aktuelle compose.yaml verwenden (wie in dieser Anleitung). docker exec python3 manage.py create-profileschlägt fehl: Das Flagenable-prepl-serverfehlt inPENPOT_FLAGS. Füge es hinzu, starte das Backend neu, wiederhole den Befehl.- IPv6-Fehler beim Frontend-Start: Auf Hosts mit deaktiviertem IPv6-Kernel-Modul kann der Nginx-Prozess im Frontend-Container nicht binden. Lösung:
PENPOT_DISABLE_IPV6_LISTEN: 'true'zur Frontend-Umgebung hinzufügen. - PostgreSQL-Daten nach Update verloren: Volume-Name-Konflikt nach dem Wechsel auf das neue Compose-File (von
penpot_postgresaufpenpot_postgres_v15). Bestehende Daten vor dem Wechsel sichern und das Volume umbenennen oder migrieren.
Häufige Fragen
Was kostet Penpot für den Self-Hosted-Betrieb?
Penpot selbst ist vollständig kostenlos (AGPL-3.0, keine Nutzerlimits, keine Lizenzkosten). Kosten entstehen nur durch die Infrastruktur: Server (VPS ab ca. 5–10 EUR/Monat), Domain und ggf. einen SMTP-Provider. Im Vergleich: Figma-Teamlizenzen kosten ab 15 EUR pro Nutzer und Monat – ab etwa 3–4 Nutzern amortisiert sich ein eigener Server mit Penpot typischerweise innerhalb weniger Monate.
Läuft Penpot auf ARM-Servern oder Apple-Silicon-Macs?
Ja. Alle offiziellen penpotapp/*-Images ab Version 2.16 unterstützen linux/arm64 nativ (je ca. 298 MB). Docker lädt automatisch die passende Architektur. Auf Apple-Silicon-Macs (M1/M2/M3) läuft Penpot über Docker Desktop ohne Rosetta-Emulation.
Wie lege ich weitere Nutzer an, wenn die Registrierung gesperrt ist?
Per CLI über den Backend-Container (das Flag enable-prepl-server muss aktiv sein):
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profileDas interaktive Skript fragt nach E-Mail-Adresse und Passwort. Alternativ kannst du Nutzer über die Einladungsfunktion im Penpot-UI per E-Mail einladen – das setzt einen funktionierenden SMTP-Server voraus.
Was ist der MCP-Server in Version 2.16?
Der neue Dienst penpotapp/mcp implementiert das Model Context Protocol und erlaubt KI-Assistenten wie Claude oder Cursor direkten Lesezugriff auf Penpot-Designs. Das ist nützlich, um z. B. Design-Tokens automatisch in Code zu übersetzen oder Design-Reviews per KI-Assistenten durchzuführen. Wichtig: Der MCP-Server erfordert genau eine Backend-Instanz – Hochverfügbarkeitsdeployments mit mehreren Backend-Instanzen sind nur ohne MCP möglich.
Kann ich Penpot mit LDAP oder OIDC verbinden?
Ja. LDAP wird über PENPOT_LDAP_*-Variablen und das Flag enable-login-with-ldap konfiguriert. OIDC (Keycloak, Azure AD, Authentik) über PENPOT_OIDC_*-Variablen. Google, GitHub und GitLab haben eigene enable-login-with-*-Flags. Die vollständige Konfigurationsreferenz findet sich in der offiziellen Penpot-Dokumentation.
Wie sichere ich meine Penpot-Daten?
Es gibt zwei kritische Datenpfade: das Docker-Volume penpot_postgres_v15 (alle Designdaten) und penpot_assets (hochgeladene Bilder und Fonts). Sichere beide regelmäßig – die PostgreSQL-Datenbank idealerweise per pg_dump für konsistente Dumps. Für Cloud-Offsite-Backups eignet sich rclone in Kombination mit einem S3-kompatiblen Dienst.
Fazit
Penpot ist eine ausgereifte Figma-Alternative, die sich mit vertretbarem Aufwand auf eigener Infrastruktur betreiben lässt. Der Docker-Compose-Stack ist gut durchdacht: Health-Checks verhindern Race Conditions beim Start, YAML-Anker halten die Konfiguration konsistent, und die Multi-Architektur-Images ermöglichen den Betrieb auf x86_64-Servern ebenso wie auf ARM-Hardware. Version 2.16 hebt mit dem integrierten MCP-Server die Latte noch einmal an – KI-Assistenten können damit direkt auf Designdaten zugreifen, was neue Automatisierungsmöglichkeiten für Entwicklungsteams eröffnet.
Für den produktiven Einsatz im DACH-Umfeld sind drei Punkte nicht verhandelbar: ein persistenter PENPOT_SECRET_KEY, eine korrekte PENPOT_PUBLIC_URI und HTTPS über einen Reverse Proxy. Wer diese drei Punkte umsetzt, bekommt eine kollaborative Designplattform ohne Lizenzkosten und ohne Cloud-Abhängigkeit.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren – die Self-Hosting-Grundlage
- Traefik als Docker-Reverse-Proxy mit automatischem HTTPS einrichten
- Docker Compose absichern: Secrets, Healthchecks und Non-Root für den Produktivbetrieb
- MySQL & PostgreSQL Backup automatisieren mit cron und rclone
Offizielle Quellen: Penpot Self-Hosting Dokumentation | Penpot Konfigurationsreferenz | Penpot GitHub Repository