Showing Posts From
Self hosted
- 14 Apr, 2026
- 4 min read
Setting up the Penpot MCP Server with Docker and Traefik (Self-Hosted)
A walkthrough of getting the official Penpot MCP server running as a Docker container behind a Traefik reverse proxy, so Claude Code (or any MCP client) can interact with a self-hosted Penpot instance. Environment:Self-hosted Penpot 2.14 via Docker Compose Traefik v2 reverse proxy Domain: custom subdomain (mcp.example.com) MCP client: Claude Code CLIArchitecture The @penpot/mcp package runs three services inside one container:Port Purpose4400 Plugin UI (Vite dev server) — serves manifest.json and plugin.js4401 MCP HTTP server — what Claude Code connects to4402 WebSocket server — what the Penpot browser plugin connects toThe browser plugin (loaded inside Penpot) connects via WebSocket to port 4402. Claude Code connects via HTTP to port 4401. The manifest and plugin JS are served from port 4400.Hickup 1: sh: pnpm: not found Problem: The penpot-mcp binary runs corepack pnpm run bootstrap on every startup. The bootstrap script spawns a subshell running pnpm -r install && pnpm run build && pnpm run start. Even after installing pnpm, the subshell couldn't find it. Attempts that failed:npm install -g pnpm — pnpm installed but not visible in bootstrap subshell corepack enable alone — creates a shim but doesn't download the actual pnpm binaryFix: Use corepack prepare pnpm@latest --activate — this actually downloads and activates the pnpm binary, not just a shim. Also switch from node:22-alpine (busybox ash) to node:22-slim (Debian) for more predictable shell PATH behavior. FROM node:22-slim ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable && corepack prepare pnpm@latest --activate && npm install -g @penpot/mcp@latestHickup 2: Vite blocks the custom domain Problem: The plugin UI (port 4400) is a Vite dev server. Vite 6+ blocks requests with a Host header that isn't localhost. When Traefik forwarded https://mcp.example.com/manifest.json to the container, Vite returned: Blocked request. This host ("mcp.example.com") is not allowed. To allow this host, add "mcp.example.com" to `preview.allowedHosts` in vite.config.js.Fix: Add a Traefik middleware that rewrites the Host header to localhost:4400 before the request reaches Vite. Vite always allows localhost. - traefik.http.middlewares.penpot-mcp-host.headers.customrequestheaders.Host=localhost:4400 - traefik.http.routers.penpot-mcp-plugin.middlewares=penpot-mcp-hostHickup 3: Plugin manifest URL rejected by Penpot Problem: Penpot plugin manager said "The plugin doesn't exist or the URL is not correct" when entering the manifest URL. Root cause 1: Using http:// URL from an HTTPS Penpot instance. Browsers block mixed content — Penpot's frontend JS cannot fetch an HTTP resource from an HTTPS page. Always use https:// for the manifest URL. Root cause 2: The WebSocket URL (ws://localhost:4402) is burned into plugin.js at Vite build time via the WS_URI environment variable. Without setting this, the plugin tries to connect to ws://localhost:4402 — which fails as mixed content from an HTTPS origin. Fix: Set WS_URI in the container environment so Vite bakes the correct WSS URL into plugin.js during the startup build: environment: WS_URI: "wss://mcp.example.com/ws"Hickup 4: Wrong environment variable names Problem: The @penpot/mcp README documents several environment variables (PENPOT_MCP_SERVER_ADDRESS, MCP_PORT, PLUGIN_PORT, etc.) that are never actually read by the source code. Setting them has no effect. Variables that actually work: PENPOT_MCP_SERVER_HOST: "0.0.0.0" # bind address for MCP HTTP server PENPOT_MCP_SERVER_PORT: "4401" # MCP HTTP port PENPOT_MCP_WEBSOCKET_PORT: "4402" # WebSocket port PENPOT_MCP_PLUGIN_SERVER_HOST: "0.0.0.0" # bind address for Vite plugin server PENPOT_MCP_REMOTE_MODE: "true" # disables local filesystem tool WS_URI: "wss://mcp.example.com/ws" # WebSocket URL baked into plugin.jsHickup 5: WebSocket needs its own Traefik route Problem: Port 4402 (WebSocket) must be reachable as wss:// from the browser (mixed content rules apply). Simply exposing port 4402 on the host doesn't help — the browser can't use plain ws:// from an HTTPS page. Fix: Add a dedicated Traefik router for /ws that proxies to port 4402. Traefik handles WebSocket upgrades natively — no special configuration needed beyond routing the path. - traefik.http.middlewares.penpot-mcp-ws-strip.stripprefix.prefixes=/ws - traefik.http.routers.penpot-mcp-ws.rule=Host(`mcp.example.com`) && PathPrefix(`/ws`) - traefik.http.routers.penpot-mcp-ws.service=penpot-mcp-ws-svc - traefik.http.routers.penpot-mcp-ws.middlewares=penpot-mcp-ws-strip - traefik.http.routers.penpot-mcp-ws.priority=30 - traefik.http.services.penpot-mcp-ws-svc.loadbalancer.server.port=4402Final Working Docker Compose Service Replace mcp.example.com with your actual domain. penpot-mcp: build: context: . dockerfile_inline: | FROM node:22-slim RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates git python3 make g++ \ && rm -rf /var/lib/apt/lists/* RUN corepack enable && corepack prepare pnpm@latest --activate RUN npm install -g @penpot/mcp@latest WORKDIR /usr/local/lib/node_modules/@penpot/mcp RUN corepack pnpm -r install CMD ["corepack", "pnpm", "run", "bootstrap"] container_name: penpot-mcp environment: PENPOT_MCP_SERVER_HOST: "0.0.0.0" PENPOT_MCP_SERVER_PORT: "4401" PENPOT_MCP_WEBSOCKET_PORT: "4402" PENPOT_MCP_REMOTE_MODE: "true" PENPOT_MCP_PLUGIN_SERVER_HOST: "0.0.0.0" WS_URI: "wss://mcp.example.com/ws" networks: - proxy-traefik-network restart: unless-stopped labels: - traefik.enable=true - traefik.docker.network=proxy-traefik-network # Router 1: MCP HTTP/SSE API (priority 20) - traefik.http.routers.penpot-mcp-api.rule=Host(`mcp.example.com`) && (PathPrefix(`/mcp`) || PathPrefix(`/sse`) || PathPrefix(`/messages`)) - traefik.http.routers.penpot-mcp-api.entrypoints=websecure - traefik.http.routers.penpot-mcp-api.tls=true - traefik.http.routers.penpot-mcp-api.tls.certresolver=myresolver - traefik.http.routers.penpot-mcp-api.service=penpot-mcp-api-svc - traefik.http.routers.penpot-mcp-api.priority=20 - traefik.http.services.penpot-mcp-api-svc.loadbalancer.server.port=4401 # Router 2: WebSocket bridge — /ws → port 4402 (priority 30) - traefik.http.middlewares.penpot-mcp-ws-strip.stripprefix.prefixes=/ws - traefik.http.routers.penpot-mcp-ws.rule=Host(`mcp.example.com`) && PathPrefix(`/ws`) - traefik.http.routers.penpot-mcp-ws.entrypoints=websecure - traefik.http.routers.penpot-mcp-ws.tls=true - traefik.http.routers.penpot-mcp-ws.tls.certresolver=myresolver - traefik.http.routers.penpot-mcp-ws.service=penpot-mcp-ws-svc - traefik.http.routers.penpot-mcp-ws.middlewares=penpot-mcp-ws-strip - traefik.http.routers.penpot-mcp-ws.priority=30 - traefik.http.services.penpot-mcp-ws-svc.loadbalancer.server.port=4402 # Router 3: Plugin UI / manifest.json / plugin.js catch-all (priority 10) # Rewrite Host header so Vite accepts the request - traefik.http.middlewares.penpot-mcp-host.headers.customrequestheaders.Host=localhost:4400 - traefik.http.routers.penpot-mcp-plugin.rule=Host(`mcp.example.com`) - traefik.http.routers.penpot-mcp-plugin.entrypoints=websecure - traefik.http.routers.penpot-mcp-plugin.tls=true - traefik.http.routers.penpot-mcp-plugin.tls.certresolver=myresolver - traefik.http.routers.penpot-mcp-plugin.middlewares=penpot-mcp-host - traefik.http.routers.penpot-mcp-plugin.service=penpot-mcp-plugin-svc - traefik.http.routers.penpot-mcp-plugin.priority=10 - traefik.http.services.penpot-mcp-plugin-svc.loadbalancer.server.port=4400Connecting Claude Code Add the MCP server globally: claude mcp add penpot -t http https://mcp.example.com/mcpOr edit ~/.claude.json directly under .mcpServers: "penpot": { "type": "http", "url": "https://mcp.example.com/mcp" }Installing the Plugin in PenpotOpen any Penpot file Main menu → Plugins → Manage plugins Add: https://mcp.example.com/manifest.json Open the plugin panel → click Connect to MCP serverOnce connected, Claude Code can create and manipulate shapes directly on the canvas.NotesThe bootstrap script runs pnpm -r install && pnpm run build && pnpm run start on every container start. Pre-installing deps in the image (RUN corepack pnpm -r install) cuts startup time significantly — the build step still runs at startup so WS_URI gets baked into plugin.js. Build time for the Docker image is ~5–10 minutes on first run (apt + npm + pnpm deps). An alternative, simpler MCP server using the Penpot REST API (no browser plugin required): zcube/penpot-mcp-server. Limitations vs @penpot/mcp: Uses the Penpot REST API — no direct canvas manipulation (can't create/move shapes in real time like the plugin API can) Self-hosted Penpot requires the enable-access-tokens feature flag set in your Penpot config before an access token can be generated Requires generating a personal access token in Penpot Settings and passing it as PENPOT_ACCESS_TOKEN No live design interaction — operations go through HTTP round-trips to the Penpot backend, not directly into the open design file

- 30 Jan, 2017
- 7 Min. Lesezeit
Eigene Streaming Seite (wie Netflix) mit Streama und einem Raspberry Pi 3
Mit Streama kannst du dir deine eigene Streaming Seite, wie Netflix, Maxdome oder Amazon Prime erstellen. Was ist der Vorteil zu einem DLNA Server wie miniDLNA oder Kodi? Du meldest dich bei deiner Streama Seite an und benötigst nur den Browser um Filme und Serien zu schauen. Das hat den Vorteil, dass du keine extra Software auf dem PC oder Tablet benötigst. Leider kann man Streama nicht mit einem TV-Gerät benutzen. Durch die Benutzerverwaltung, wird für jeden Benutzer extra gespeichert, welche Filme und Serien er schon geschaut hat und wo er als letztes aufgehört hat zu schauen.Es ist möglich, deine Streama Seite online verfügbar zu machen (Portweiterleitung), um von überall aus deine Filme und Serien per Stream schauen zu können. Du benötigst einen Rechner, der möglichst immer ein geschalten ist (Raspberry Pi 3, Linux Debian Server). Du kannst Streama auch auf deinem Windows oder Mac OSX Rechner installieren.Cover und Beschreibung von Serien/Filmen werden automatisch von TheMovieDB geladen Schöne und Übersichtliche Darstellung Eigene Benutzerverwaltung, Zugriff auf die Mediathek mit Passwortschutz. Streama merkt sich wo man einen Film aufgehört hat zu schauen und startet beim nächsten mal an genau dieser Stelle wieder. Vorschläge für ähnliche Serien wie du in der Mediathek hast. Der Quellcode ist Open Source und wird weiterentwickelt. geplante Funktionen: Videokonvertierung, Media Crawler und Chromecast Unterstützung. Videos müssen HTML5 kompatibel sein, oder müssen konvertiert werden. Java wird benötigt und benötigt recht viel Rechenleistung (min. Raspberry Pi 3)Inhalt Voraussetzungen:Raspberry Pi oder ein Debian Linux Rechner Betriebssystem Raspbian oder Debian Linux Wifi- oder Lan-VerbindungJava Development Kit 8 installieren Streama benötigt Java. Mit folgendem Befehl aktualisierst du die Paketlisten von Raspbian/Debian und installierst danach Java Development Kit 8. sudo apt-get update && sudo apt-get install oracle-java8-jdkStreama installierenErstell ein Ordner mit dem Namen streama im Heimverzeichnis des aktuellen Benutzers und navigieren in den neuen Ordner. mkdir ~/streama && cd ~/streamaLade dir die aktuelle .war Datei von Streama herunter. Als ich den Artikel geschrieben habe, war die Version 1.0.11 gerade aktuell. Schau auf dieser Seite nach welche Version gerade aktuell ist, und ersetze die Versionsnummer mit der im Befehl. wget https://github.com/dularion/streama/releases/download/v1.0.11/streama-1.0.11.war Eine .war Datei (Web Application Archive) legt fest, wie eine komplette Webanwendung nach der Java-Servlet-Spezifikation in eine ZIP- bzw JAR-Datei verpackt wird. Mehr Informationen findest du hier.Du benötigst noch die sample_application.yml Datei. Diese muss im selben Ordner gespeichert werden wie die .war Datei und in application.yml umbenannt werden. wget -O application.yml https://raw.githubusercontent.com/dularion/streama/master/docs/sample_application.yml In der application.yml werden Streama Einstellungen festgelegt wie Server Port, Benutzername und Passwort. Da auf meinem Raspberry Pi der Port 8080 schon belegt ist, ändere ich den Standard Streama Port von 8080 auf 8090. Öffne dazu die application.yml Datei mit dem Nano Texteditor und ändere die Zeile port: 8080 in port: 8090. Schließe den Texteditor mit ctrl+x (strg + x) und speichere die Änderung mit y ENTER. sudo nano application.ymlMach die Streama .war Datei ausführbar, damit sie später automatisch als Service gestartet werden kann (Versionsnummer ersetzen). sudo chmod u+x streama-1.0.11.warStarte Streama mit folgendem Befehl. Ersetze wieder die Versionsnummer mit der zuvor heruntergeladenen Version. java -jar streama-1.0.11.war Jetzt kannst du die Streama Weboberfläche aufrufen indem du im Browser http://192.168.0.10:8080 aufrufst. Ersetze die IP mit der deines Raspberry Pi. Falls du den Port geändert hast, pass diesen noch an. Bei einem Raspberry Pi dauert das Starten von Streama einige Zeit (Raspberry Pi 3 60Sekunden). Wenn im Terminal die Zeile Grails application running at http://localhost:8080 in environment: production erscheint, dann läuft Streama. Melde dich mit den standard Benutzerdaten admin/admin an. Streama Einstellungen und TheMovieDB API Key einrichten Nach dem anmelden wirst du aufgefordert ein paar Grundeinstellungen für Streama festzulegen. Klick auf OK und du wirst zur Einstellungsseite weitergeleitet. Streama Einstellungen:Upload Directory (erforderlich): Legt den Ordner fest in den alle Dateien/Videos hochgeladen werden. Falls du noch keinen Ordner für deine Filme und Serien hast, erstelle einen Ordner in deinem Heimverzeichnis mit mkdir ~/media. Den Pfad zu dem Ordner erhältst du, indem du mit cd ~/media in Ordner navigierst. Der Befehl pwd gibt dir dann den Pfad aus. Füge diesen dann unter der Einstellung Upload Directory ein.TheMovieDB API key: Hier trägst du dein API Schlüssel ein den du unter themoviedb.org/account/signup beantragen kannst. Streama zeigt dir dann Informationen und Cover deiner Filme und Serien an. Nach der Registrierung klicke rechts oben auf deinen Benutzernamen, danach links auf API, API Schlüssel anfordern und wähle dann Developer. Leider müssen für den API Key einige Daten angegeben werden. Ob du echte Daten angibst bleibt dir überlassen. Kopier den API Schlüssel (v3 auth) und füge ihn in Streama ein. Mit Validate kannst du prüfen ob der Key funktioniert.Base URL (erforderlich): Hier kannst du die URL ändern unter der Streama aufgerufen werden kann. Trage hier die IP des Rechners ein auf dem Streama läuft (z.B. http://192.168.0.10:8080). Falls du den Port von Streama geändert hast musst du ihn hier auch ändern.Second Directory: Füge eine oder mehrere Ordner hinzu, teile mehrere Ordner mit | (Beispiel: /data/streama|/mnt/streama). Diese Ordner können für vorherig hochgeladene Datei verwendet werden. Das kann nützlich sein, wenn du deine Mediathek auf mehrere Speichermedien aufteilen willst.Local Video Files: Wenn du deine Mediathek schon in einem anderen Ordner hast, oder auf einem Speichermedium kannst du hier den Pfad angeben. Wenn du in Streama eine neue Serie oder Film erstellst, kannst du dann die Dateien von diesem Ordner wählen.First Time Login Info: Wenn aktiviert, wird der Hinweis mit den Standard Login Daten eingeblendet.Streama automatisch starten beim Hochfahren (als Service) Um Streama im Hintergrund automatisch zu starten, kannst du es als Service installieren/einrichten. Dazu richten wir Streama als Service in systemctl ein.Navigiere in den Ordner, in dem die Streama .war Datei liegt. cd ~/streamaErstell einen Link zur .war Datei ohne die Versionsnummer. Falls du später Streama aktualisierst, musst du nur den Link ändern und nicht die Service Datei umschreiben. Ersetze die Versionsnummer im Befehl mit der der Versionsnummer deiner Version. ln -s streama-1.0.11.war streama.warSpäter benötigst du den aktuelle Benutzernamen und den kompletten Pfad in dem deine Streama .war Datei liegt. Führe den Befehl whoami aus und notiere dir den Benutzernamen. Mit dem Befehl pwd gibst du den aktuellen Pfad aus. Notiere dir diesen ebenfalls.Der nachfolgende Befehl erstellt eine systemctl Service Datei und öffnet diese im Nano Texteditor. sudo nano /etc/systemd/system/streama.serviceFüge folgenden Text ein und ersetze User=pi mit deinem Benutzernamen. Ersetze auch ExecStart=/data/streama/streama.war und ConditionPathExists=/data/streama/streama.war mit dem zuvor ermittelten Pfad zur Streama .war Datei.[Unit] Description=streama After=syslog.target[Service] User=pi ExecStart=/data/streama/streama.war SuccessExitStatus=143 ConditionPathExists=/data/streama/streama.war[Install] WantedBy=multi-user.target Beende den Texteditor mit ctrl+x (strg + x) und speichere die Änderung mit y ENTER.Aktiviere den systemctl Service. sudo systemctl enable streama.serviceJetzt kannst du Streama start. sudo systemctl start streama.service So kannst du den Streama Service stoppen sudo systemctl stop streama.service und so neustarten. sudo systemctl restart streama.serviceNach einem Neustart sollte Streama jetzt automatisch Starten. Sprache auf Deutsch ändern Streama unterstützt folgende Sprachen: Englisch, Deutsch, Spanisch, Französisch, Holländisch, Koranisch und Portugiesisch. Wenn du auf deiner Streama Seite angemeldet bist, kannst du oben rechts unter DEIN Benutzername -> Profile Settings die Sprache ändern. Unterstützte Video Formate und Codecs Streama konvertiert deine Videos noch nicht. Wenn ein Video nicht abgespielt wird, ist es nicht HTML5 kompatibel. Hier gibt es eine Übersicht der HTML5 kompatiblen Videoformate/Codecs und welcher Browser sie unterstützt. Probier Chrome und Firefox falls ein Video nicht abgespielt wird. Wenn du ein Video in das Browserfenster ziehst und es wird abgespielt, ist es HTML5 kompatibel. Wo sind meine Daten gespeichert und wie stell ich auf MySQL um Streama verwendet eine eingebettet persistente Datenbank namens H2 in der .war Datei (Streama.db). Deine Daten sind auch nach einem Neustart noch verfügbar. Falls du eine MySQL Datenbank bevorzugst, kannst du in der application.yml die MySQL Zugangsdaten angeben. Entferne das # Kommentarzeichen am Anfang der Zeilen und trage deine MySQL Zugangsdaten ein. Mit den Standardwerten muss deine Datenbank unter root'@'localhost mit dem Datenbanknamen streama erreichbar sein.