Webstudio selbst hosten hinter Caddy: Drei Stunden Lehrgeld in einer Setup-Story

01.05.2026Aktuell, Anleitungen

Webstudio ist ein hervorragender visueller Web-Builder — Open Source, gut gepflegt, mit echtem Designer-Feeling. Was die offizielle Doku eher beiläufig erwähnt: Self-Hosting hinter einem Reverse-Proxy ist explizit nicht supportet. Wir haben das auf die harte Tour gelernt. Dieser Artikel ist der ehrliche Erfahrungsbericht — was wir versucht haben, woran es scheiterte, und welcher Pfad am Ende funktioniert hat.

Lesedauer ca. 10 Minuten. Zielgruppe: Entwickler, die Webstudio auf einer eigenen Subdomain mit Caddy oder Traefik laufen lassen wollen.

Nur am Setup interessiert, ohne Story? Spring direkt zu V2-Setup: was am Ende funktioniert hat — dort steht die kompakte Architektur-Beschreibung mit Diagramm und Verifikations-Schritten.

TL;DR für Eilige

  1. Nicht webstudio-is/webstudio direkt klonen und mit pnpm dev betreiben — der Vite-Dev-Server hat wstd.dev hardcoded.
  2. Stattdessen den Community-Fork verwenden — webstudio-community/webstudio-self-host (GitHub): Production-Remix-Build, sauberes APP_FQDN per ENV, offiziell vom Webstudio-FAQ verlinkt.
  3. Fertiges Template zum Mitnehmenweb-werkstatt/webstudio-self-host-werkstatt (GitHub): kompletter Compose-Stack mit Caddy-Config, basic_auth, MinIO, Init-Containern und Troubleshooting-Doku, basierend auf den Erkenntnissen aus diesem Artikel.
  4. Architektur: 7 Services (App, Postgres, PostgREST, MinIO + 3 Init-Container), alle hinter Caddy mit basic_auth.
  5. Update-Sicherheit: BUILDER_IMAGE mit SHA pinnen, nicht :latest.
  6. Mit dem richtigen Pattern dauert das Setup ca. 30 Minuten — ohne war es bei uns ein Dreistünder.

Ausgangslage

Was wir wollten: Webstudio Builder als visuelle Designwerkstatt auf einer eigenen Subdomain (webstudio.example.com), hinter Caddy mit basic_auth abgesichert, für Single-User-Authoring.

Was wir hatten:

  • Hetzner-VM, Debian 12, Docker 24 mit Compose v2
  • Caddy als Reverse-Proxy schon produktiv im Einsatz
  • Plan-Annahme: „Webstudio ist Open Source, also klone ich das Repo und starte pnpm dev in einem Container — fertig in einer Stunde.“

Spoiler: das hat nicht funktioniert. Drei Stunden später und mit einem komplett anderen Ansatz lief es. Hier ist der Verlauf.

V1 — Plan: Source-Klon plus pnpm dev im Dev-Mode-Container

Was wir gemacht haben

Die Idee war pragmatisch: Das offizielle webstudio-is/webstudio-Repo lokal klonen, in einem node:22-alpine-Container mit Volume-Mount betreiben, pnpm install + dev als Container-Command, fertig.

webstudio-builder:
  image: node:22-alpine
  volumes:
    - /path/to/webstudio-clone:/app
    - webstudio_node_modules:/app/node_modules
  environment:
    DATABASE_URL_FILE: /run/secrets/database_url
    AUTH_SECRET_FILE: /run/secrets/auth_secret
  command: >
    sh -c "pnpm install && pnpm migrations && pnpm --filter=@webstudio-is/builder dev"

Dazu Postgres und PostgREST in zwei weiteren Services. Schien sauber.

Stolperstein 1 — Untracked Files im Klon

git status zeigte:

webstudio-ai-service/   (eigenes Sub-Projekt, ~50 Dateien, Oktober 2025–Januar 2026)
TODO.md
.pnpm-store/
.coderabbit.yaml
.github/workflows/{coderabbit,security-review,code-quality}.yml
+24 weitere project.json (Nx-Workspace-Setup)

Der Klon war nicht „jungfräulich“ — früheres Tooling-Setup plus ein parallel angelegtes AI-Microservice-Projekt im selben Verzeichnis. Erste Diskussion: was tun mit der eigenen Substanz?

Lösung: Pragmatisch — git pull schadet untracked Files nicht. Der Container ignoriert fremde Verzeichnisse, weil pnpm-workspace strikt ist (sieht nur, was in pnpm-workspace.yaml steht).

Stolperstein 2 — PostgREST 12 kennt kein _FILE-Suffix

Wir hatten das Postgres-Passwort als Docker-Secret geplant, mit POSTGRES_PASSWORD_FILE (Postgres-Image-Konvention). Naheliegend, dasselbe für PostgREST zu machen:

PGRST_DB_URI_FILE: /run/secrets/postgrest_db_uri

Container startete und ging in eine Restart-Loop:

PGRST000: could not look up local user ID 1000: No such file or directory

Diagnose: PostgREST 12.x ignoriert _FILE-Suffix-Variablen. Das ist Postgres-Image-Konvention, nicht universell. PostgREST liest nur die direkten ENV-Vars; wenn PGRST_DB_URI fehlt, fällt libpq auf Peer-Auth zurück und sucht UID 1000 in /etc/passwd — existiert im Container nicht → Fehler.

Lösung: Inline-ENV-Substitution mit ${VAR} aus einer .env-Datei neben dem Compose:

webstudio-postgrest:
  environment:
    PGRST_DB_URI: "postgresql://postgres:${POSTGRES_PASSWORD}@webstudio-db:5432/webstudio"

Postgres bleibt mit _FILE (funktioniert dort), App und PostgREST nutzen ${VAR}-Pattern.

Stolperstein 3 — Bind-Mount zeigt ins Leere

Nach dem Compose-Fix ging der Builder-Container hoch und brach sofort ab:

ERR_PNPM_NO_PKG_MANIFEST  No package.json found in /app

Diagnose: Der lokale Klon lag auf meinem Laptop unter /mnt/projects/webstudio-builder/. Compose mountete /mnt/projects/webstudio-builder:/app — aber der Container läuft auf der Server-VM. Die VM hat den Pfad nicht.

Ein Konzeptfehler im ursprünglichen Plan: Pfade waren so notiert, als wären Laptop und Server der gleiche Host.

Lösung: Klon auf die VM legen, neuen Pfad mounten:

ssh server 'sudo mkdir -p /data/webstudio/source && \
  sudo chown joshko:joshko /data/webstudio/source && \
  cd /data/webstudio && \
  git clone https://github.com/webstudio-is/webstudio.git source'

Stolperstein 4 — Workspace-Packages nicht gebaut

Container startet weiter. Beim Start von Vite:

Failed to resolve entry for package "@webstudio-is/http-client".
The package may have incorrect main/module/exports specified in its package.json.

Diagnose: Webstudio ist ein pnpm-Monorepo. Internal-Workspace-Packages müssen vor pnpm dev gebaut sein. Der offizielle .devcontainer/postinstall.sh zeigt die Reihenfolge:

pnpm install
pnpm build               # ← der fehlte uns
pnpm migrations migrate  # ← richtig: "migrate" Sub-Command, nicht "migrations" allein

Lösung: Container-Command erweitert, idempotent (Build erkennt unveränderte Files):

pnpm install --frozen-lockfile=false &&
pnpm build &&
(pnpm migrations migrate || true) &&
pnpm --filter=@webstudio-is/builder dev --host 0.0.0.0 --port 5173

Das läuft 5+ Minuten beim ersten Start (pnpm install plus pnpm build über ~50 Workspace-Packages), bei Restarts 30 Sekunden (alles cached).

Stolperstein 5 — Der Showstopper

Nach erfolgreichem Build:

Local:   https://wstd.dev:5173/
Local:   https://vite.wstd.dev:5173/

Vite startete — aber auf wstd.dev. Hardcoded. Curl gegen https://webstudio-builder:5173/:

  • Ohne Host-Header: TLS-Reset
  • Mit Host: wstd.dev: HTTP 403 mit leerem Body
// apps/builder/vite.config.ts:
server: {
  host: "wstd.dev",                    // hardcoded
  https: {
    key: readFileSync("../../https/privkey.pem"),    // Self-Signed Cert für wstd.dev
    cert: readFileSync("../../https/fullchain.pem"),
  },
  cors: req => callback(null, { origin: false }),    // CORS aus
}

Plus Auth-Middleware, die Origin gegen wstd.dev prüft.

Webstudios offizielle Doku sagt es klar:

Self-hosting the Builder in production is more difficult and currently not recommended.

Der Dev-Server ist auf lokales Setup mit /etc/hosts-Mapping 127.0.0.1 wstd.dev plus Self-Signed Cert designed. Reverse-Proxy auf eigener Domain ist explizit nicht supportet.

Optionen, die wir verworfen haben

OptionWarum nicht
Vite-Config patchen (host, https, cors)Klon-Drift; bei git pull Konflikte; OAuth-Pfade brechen weiter
Caddy header_up Host wstd.dev + tls_insecure_skip_verifyBrowser-Redirects/Links zeigen auf wstd.dev → unerreichbar
Production-Build (remix-serve) aus SourceKein offizielles Image, eigener Build nötig, Test-Aufwand offen
Akzeptieren, dass V1 nicht geht

V2 — Recherche-Pivot

An dem Punkt war klar: Brute-Force funktioniert nicht. Wir haben einen Recherche-Agent losgeschickt mit konkretem Auftrag:

Findet jemand, der Webstudio-Self-Hosting hinter Reverse-Proxy tatsächlich am Laufen hat? GitHub-Issues, Discord, Forks.

Antwort nach 2 Minuten: Es existiert ein Community-Fork.

  • Repo: webstudio-community/webstudio-self-host
  • Image: ghcr.io/webstudio-community/builder:latest
  • Production-Remix-Build (kein Vite-Dev), wstd.dev-Hardcodes raus, APP_FQDN als saubere ENV-Konfiguration
  • Wird offiziell vom Webstudio-FAQ verlinkt (im Self-Hosting-Abschnitt)

Game-Changer. Re-Setup mit dem Image dauerte ca. 30 Minuten und lief beim ersten Versuch sauber durch.

V2-Setup: was am Ende funktioniert hat

Architektur

Topologie auf einen Blick:

                ┌─────────────────────────────────────┐
                │   Caddy   (HTTPS + basic_auth)      │
                └──────────────────┬──────────────────┘
                                   ↓
                ┌─────────────────────────────────────┐
                │      webstudio-app   (Remix)        │
                └──────┬───────────────────────┬──────┘
                       ↓                       ↓
            ┌──────────────────────┐  ┌──────────────────┐
            │ webstudio-postgrest  │  │ webstudio-minio  │
            └──────────┬───────────┘  │     (S3)         │
                       ↓              └──────────────────┘
            ┌──────────────────────┐
            │ webstudio-db (PG 15) │
            └──────────────────────┘

   Run-once-Init:   db-setup  →  migrate  →  minio-init

7 Services im Detail:

  • webstudio-app (Production-Image vom Community-Fork)
  • webstudio-db (postgres:15-alpine plus postgres-init.sql-Bind-Mount für extensions-Schema und anon-Rolle)
  • webstudio-postgrest (postgrest/postgrest:v12.2.0)
  • webstudio-minio (S3-Asset-Storage)
  • 3 Run-Once-Init-Container (db-setup, migrate, minio-init)

Alle in proxy-network extern, kein Host-Port-Binding, Caddy mit basic_auth davor.

Inkrementelle Verifikation — was wirklich half

Statt alle 7 Services parallel hochzuziehen: einer nach dem anderen.

docker compose up -d webstudio-db
# verify pg_isready, dann:
docker compose up -d webstudio-postgrest
# verify "Successfully connected to PostgreSQL", dann:
docker compose up -d webstudio-app
# verify /health endpoint

Bei Stolperstein 2 (PostgREST _FILE-Suffix) hatten wir die Diagnose sofort, weil nur ein neuer Service hinzugekommen war. Bei einem parallelen up wäre die Fehlersuche viel länger gewesen.

Stolperstein 6 — Postgres-Schema-Migration zwischen Image-Wechseln

Zwischen V1 (Supabase-Postgres-Image) und V2 (plain postgres:15-alpine) ist das Daten-Volume inkompatibel — anderer init.

Lösung: Volume umbenennen statt löschen — reversibel:

mv /data/webstudio/postgres /data/webstudio/postgres.OLD-supabase-2026-05-01
mkdir -p /data/webstudio/db

Nach erfolgreichem V2-Setup könnte man *.OLD* löschen — wir ließen es aus Vorsicht.

Stolperstein 7 — Origin-Check bei wrong Host

Mit Caddy davor und richtigem APP_FQDN=webstudio.example.com lief alles. Direkt-curl gegen den Container intern:

curl http://webstudio-app:3000/   # HTTP 403

War kurz Schreck-Moment, bis im Log:

Cross-origin request to http://webstudio-app:3000/ blocked
[ ['host', 'webstudio-app:3000'] ]

Erwartetes Verhalten. Das Image prüft den Host-Header gegen APP_FQDN. Caddy reicht den richtigen Host durch, dann kommt 200/302.

Was uns geholfen hat

Inkrementelles Hochziehen

Service für Service starten, jeweils healthy verifizieren, dann erst weiter. Spart Stunden bei der Fehlersuche.

Beweisblock am Ende jedes Schritts

Nach jedem up wurde geprüft:

  • docker ps zeigt Status
  • pg_isready bzw. wget /health als interne Probe
  • Logs nach Errors gegrept

Wenn ein Schritt fehlschlug, war klar welcher und wo. Ohne diese Disziplin verliert man sich in „irgendwas geht nicht“.

Reversible Aktionen statt destruktive

  • mv statt rm für alte Daten
  • docker compose stop statt down -v
  • Backup vor jedem irreversiblen Schritt

Nichts ging während des Setups verloren, weil jeder Schritt rückwärts abrollbar war.

Stufe-3-Vorgehen mit manueller Freigabe

Manuelle Freigabe pro Schritt, kein Auto-Run, vor jedem Compose-Edit oder Caddy-Deploy kurz „was und warum“ erklären. Kostet Zeit, spart Zeit — Fehler werden früh gefangen, nicht erst nach 30 Minuten.

Recherche-Agent statt Brute-Force

Als V1 stockte: nicht weiter probieren, sondern fragen, ob das Problem bekannt ist und ob es Forks gibt. Das fand den Community-Fork in 2 Minuten.

Was wir lieber anders gemacht hätten

Webstudio-Doku zuerst gründlich lesen

Die Warnung „Self-hosting the Builder in production is more difficult and currently not recommended“ stand schwarz auf weiß in der offiziellen Doku. Wir hatten sie gelesen und interpretiert als „aufwendig, aber machbar“. Stimmt nicht — sie heißt „eigentlich nicht supportet, geh über Forks“. Erkenntnis: Doku-Warnungen ernster nehmen.

Image-Tag von Anfang an pinnen

Wir hatten zuerst :latest benutzt — funktioniert, aber bei späteren Updates riskant. Im finalen Setup ist die SHA-Pin-Empfehlung in docs/DEPLOY.md explizit drin.

Nicht zu viele Compose-Re-Designs

Wir hatten zwischen V1-Iterationen drei verschiedene Compose-Dateien geschrieben. Im Nachhinein: nach Stolperstein 5 sofort Recherche-Pivot, nicht erst weitere Source-Klon-Versuche.

Zeitaufwand grob

PhaseDauer
V1 Source-Klon-Setup mit Stolpersteinen 1–5ca. 2 h
Recherche-Pivotca. 10 Min
V2 Self-Host-Patternca. 30 Min
Caddy plus Verifikationca. 15 Min
Gesamtca. 3 h

Mit dem Wissen, das wir jetzt haben, ist das Setup in ca. 30 Minuten machbar — vorausgesetzt Server, DNS und Caddy/Traefik sind schon vorhanden.

Das Repo zum Mitnehmen

Aus dieser Setup-Story ist ein vollständiges Template-Repo entstanden, das genau diesen Stolperstein-Parcours überspringt:

👉 github.com/web-werkstatt/webstudio-self-host-werkstatt

Enthalten sind:

  • docker-compose.yml mit allen 7 Services, korrekten ENV-Patterns (${VAR} statt _FILE wo nötig) und Init-Container-Reihenfolge
  • Beispiel-Caddy-Config mit basic_auth und korrektem Host-Header-Forwarding
  • .env.example mit allen Pflicht-Variablen (APP_FQDN, BUILDER_IMAGE mit SHA-Pin-Empfehlung, MinIO-Credentials)
  • postgres-init.sql für extensions-Schema und anon-Rolle
  • TROUBLESHOOTING.md mit allen sieben Stolpersteinen und konkreten Diagnose-Befehlen
  • docs/DEPLOY.md mit Schritt-für-Schritt-Verifikation und Update-Workflow

Issues und Pull Requests sind willkommen — wenn ihr eigene Stolpersteine findet, gerne ergänzen.

Keine Lust auf DIY? Ich hoste es dir.

Self-Hosting ist für Leute, die gern selbst an Docker, Caddy und Postgres schrauben — und genau dafür ist das Repo oben gemacht. Wenn du Webstudio aber einfach nutzen willst, ohne dich mit Compose-Files, Reverse-Proxy-Configs und Init-Containern zu beschäftigen: ich übernehme das gerne.

Was du bekommst:

  • Webstudio Builder auf deiner eigenen (Sub-)Domain, abgesichert per basic_auth oder OAuth
  • Hetzner-Infrastruktur in der EU — DSGVO-konform, kein US-Cloud-Drittland-Theater
  • Tägliche Postgres-Backups, MinIO-Snapshots, kontrollierte Image-Updates mit SHA-Pin
  • Direkter Ansprechpartner per Mail oder Telefon — kein Ticket-System, kein Tier-2-Outsourcing

Ich betreibe seit Jahren 130+ Docker-Container für eigene Projekte und Kunden auf exakt der Stack-Kombination, die in diesem Artikel beschrieben ist (Hetzner + Caddy + Postgres + MinIO). Webstudio läuft seit ich es selbst nutze — der Self-Host-Setup ist also keine theoretische Übung, sondern produktive Tooling-Infrastruktur, die ich täglich verwende.

Unverbindliche Anfrage oder ein kurzes Gespräch zum Thema: web-werkstatt.at — Joseph Kisler, Solo-Entwickler aus Wels, Oberösterreich. Ich statt wir, kein Agentur-Overhead.

Fazit

Webstudio ist ein großartiges Tool — aber Self-Hosting ist kein Standardpfad, sondern ein Spezialfall, den die Maintainer bewusst nicht priorisieren. Wer das ignoriert und mit dem Source-Repo gegen die Wand läuft, verliert garantiert ein paar Stunden.

Die wichtigste Lektion war keine technische, sondern eine methodische: Wenn die offizielle Doku eine Warnung ausspricht, lohnt es sich, vor dem ersten docker compose up 10 Minuten in Recherche zu investieren — speziell nach Community-Forks, die genau dieses Szenario adressieren. Das hätte uns 2,5 von 3 Stunden gespart.

Der zweite Take-away: Inkrementelle Verifikation ist kein Overhead, sondern Versicherung. Service für Service hochziehen, jeweils gegen ein konkretes Health-Signal prüfen, dann weiter — diese Disziplin macht den Unterschied zwischen einem 30-Minuten-Setup und einem Nachmittag voller Restart-Loops.

Das könnte Sie auch interessen.