Supabase mit Docker auf einem VPS selbst hosten
Betreiben Sie den offiziellen Supabase-Docker-Stack auf Ihrem VPS. Ersetzen Sie Demo-Secrets, verstehen Sie 14 Dienste, planen Sie RAM, Backups und Updates ohne Datenverlust.
Was Sie aufbauen
Self-Hosting von Supabase bedeutet, den offiziellen Docker-Compose-Stack auf Ihrem eigenen Server auszuführen: Postgres, eine REST-API davor, ein Authentifizierungsdienst, Dateispeicher, Realtime-WebSockets und das Studio-Dashboard. Sie klonen ein Repository, bearbeiten eine .env-Datei und starten etwa vierzehn Container, die zusammen wie ein von Ihnen kontrolliertes Supabase-Projekt funktionieren.
Die Installation ist kurz. Probleme entstehen meist durch die .env-Datei. Sie enthält Demo-Secrets, die im Repository veröffentlicht sind. Ein Stack, der mit diesen Standardwerten gestartet wird, ist für jeden offen, der ihn findet. Dieser Leitfaden beschreibt, welche Secrets Sie ersetzen müssen, wofür die einzelnen Dienste zuständig sind, wie viel Arbeitsspeicher der Stack tatsächlich benötigt und wie Sie ihn aktualisieren, ohne Ihre Datenbank zu löschen.
Wenn Compose für Sie neu ist, lesen Sie zuerst Docker-Compose-Grundlagen auf einem VPS. Im Folgenden wird vorausgesetzt, dass docker compose version bereits eine Version ausgibt.
Was der Stack tatsächlich enthält
Supabase ist kein einzelnes Programm. Die Compose-Datei startet mehrere separate Dienste in einem Netzwerk. Wenn Sie die Zuständigkeiten der einzelnen Dienste kennen, wird aus einer langen Liste von Containern eine überschaubare Grundlage für die Fehlersuche.
dbist PostgreSQL mit geladenen Supabase-Erweiterungen. Alle anderen Dienste kommunizieren damit. Wenn dieser Container nicht fehlerfrei ist, fallen auch alle anderen Dienste aus.kongist das API-Gateway. Es lauscht auf Port 8000 und leitet/rest/v1/,/auth/v1/und/storage/v1/an das jeweils richtige Backend weiter. Nur diesen Container sollten Sie jemals nach außen veröffentlichen.restist PostgREST. Der Dienst liest Ihr Postgres-Schema und stellt es als REST-API bereit. Dadurch wird eine neue Tabelle ohne zusätzlichen Code zu einem neuen Endpunkt.authist GoTrue. Der Dienst stellt die JSON Web Tokens (JWT) aus, mit denen Ihre Benutzer identifiziert werden.storageundimgproxyverarbeiten Datei-Uploads und skalieren Bilder.realtimeüberträgt Datenbankänderungen über WebSockets.studioundmetasind das Dashboard und die dahinterliegende Admin-API.analytics(Logflare) undvectorsammeln Logs.supavisorist der Pooler für PostgreSQL-Verbindungen.
Diese Liste erklärt, warum die folgenden Ressourcenwerte so hoch sind. Sie betreiben nicht nur eine Datenbank. Sie betreiben eine Datenbank plus ein Dutzend unterstützende Dienste.
Sizing: Planen Sie 8 GB RAM ein
Der Stack belegt bei einer frischen Installation im Leerlauf ungefähr 2.5 bis 3 GB residenten Speicher, Stand Juli 2026, noch ohne eigene Daten oder Netzwerkverkehr. Der Analytics-Dienst und der Studio-Node.js-Prozess sind die beiden größten Einzelverbraucher. Ein Server mit 2 GB startet die Container und verliert anschließend einen davon an den Kernel-OOM-Killer, normalerweise analytics oder db. Das äußert sich durch einen Container, der mit Exit-Code 137 ständig neu gestartet wird.
Planen Sie für alles, worauf Sie sich verlassen, 8 GB RAM und 4 vCPU ein. 4 GB reichen für eine einzelne Entwicklungsinstanz aus, wenn Sie akzeptieren, dass eine umfangreiche Abfrage und eine Studio-Sitzung gleichzeitig langsam sind. Auch der Speicherplatz ist wichtig, weil Postgres, das Storage-Volume und die Logdaten alle unter dem Projektverzeichnis liegen. Beginnen Sie mit 40 GB und überwachen Sie die Auslastung. Die Dienste zu zählen, bevor Sie einen Tarif auswählen, ist eine sinnvolle Gewohnheit für alles, was Sie selbst hosten, da PhotoPrism und Immich tatsächlich deutlich mehr RAM benötigen, als ihre Quickstart-Seiten vermuten lassen.
Installieren: Offizielles Repository klonen
Der unterstützte Weg kopiert das Verzeichnis docker aus dem Haupt-Repository in ein eigenes Projektverzeichnis. Diese Trennung ist wichtig, weil ein späteres git pull dadurch Ihr .env nicht überschreiben kann.
git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pulldocker compose pull lädt mehrere Gigabyte an Images herunter. Am Ende sollte jeder Dienst mit dem Status Pulled gekennzeichnet sein. Ein manifest unknown-Fehler bedeutet hier, dass der festgelegte Image-Tag im Upstream-Repository entfernt wurde. Die Lösung besteht darin, eine neuere Kopie des Repositorys abzurufen, statt die Tags manuell zu bearbeiten.
Die Secrets, die Sie vor dem ersten Start ändern müssen
Führen Sie diese Schritte vor dem Start des Stacks aus, nicht danach. Mehrere dieser Werte werden beim ersten Start in Daten geschrieben. Eine spätere Änderung erfordert daher ein Zurücksetzen der Datenbank.
Das Repository enthält einen Generator, der jeden Wert korrekt erzeugt. Dazu gehören auch die beiden API-Schlüssel, die mit Ihrem neuen JWT-Secret signiert werden müssen.
sh utils/generate-keys.sh --update-envDas Skript schreibt neue Werte für JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, SECRET_KEY_BASE, REALTIME_DB_ENC_KEY, VAULT_ENC_KEY, PG_META_CRYPTO_KEY und die Logflare-Tokens in .env. Es benötigt openssl. Dieses ist in jedem normalen Ubuntu-Image vorhanden.
Zwei Werte setzt das Skript nicht. Diese müssen Sie in .env manuell bearbeiten:
POSTGRES_PASSWORD. Verwenden Sie ausschließlich Buchstaben und Ziffern. Satzzeichen führen hier zu Fehlern in den Verbindungszeichenfolgen, die mehrere Dienste durch das Zusammenfügen von Zeichenfolgen erstellen. Der Fehler sieht dann wie ein Authentifizierungsfehler und nicht wie ein Parsing-Fehler aus. Dadurch wird häufig an der falschen Stelle nach der Ursache gesucht.DASHBOARD_USERNAMEundDASHBOARD_PASSWORD. Dies sind die Zugangsdaten für die Basic Authentication von Studio. Das mitgelieferte Standardpasswort lautet wörtlichthis_password_is_insecure_and_should_be_updated.
Sie müssen verstehen, warum ANON_KEY und SERVICE_ROLE_KEY nicht frei erfunden werden können. Beide sind JWTs, die mit JWT_SECRET signiert werden. Das Gateway prüft diese Signatur bei jeder Anfrage. Ein Schlüssel, der nicht zu Ihrem Secret passt, wird daher mit {"message":"Invalid authentication credentials"} abgelehnt. Dies ist der häufigste Fehler beim Self-Hosting: Der Betreiber ändert JWT_SECRET, behält aber die Demo-Schlüssel bei. Erzeugen Sie immer alle drei Werte gemeinsam.
Behandeln Sie SERVICE_ROLE_KEY wie ein root-Passwort. Dieser Wert umgeht die Row-Level-Security vollständig. Er gehört in serverseitigen Code und nirgendwo sonst.
Setzen Sie SITE_URL und API_EXTERNAL_URL auf die Adresse, unter der Ihre Benutzer den Dienst tatsächlich erreichen, zum Beispiel https://supabase.example.com. Auth erstellt daraus Links zur E-Mail-Bestätigung und zu OAuth-Callbacks. Wenn Sie die Werte auf http://localhost:8000 belassen, werden alle Benutzer auf ihren eigenen Rechner umgeleitet.
Prüfen Sie anschließend Ihre Einstellungen:
sh run.sh secretsStarten und den fehlerfreien Betrieb bestätigen
sh run.sh start
docker compose psrun.sh start startet docker compose up -d --wait und kehrt erst zurück, wenn die Healthchecks erfolgreich sind. Jeder Dienst sollte running (healthy) oder running anzeigen. Der erste Start dauert zwei bis vier Minuten, weil Postgres seine Initialisierungsskripte ausführt, bevor eine andere Komponente eine Verbindung herstellen kann.
Wenn ein Container neu gestartet wird, lesen Sie seine Logs anhand des Servicenamens:
docker compose logs db
docker compose logs authStudio ist anschließend über Port 8000 erreichbar und fordert den von Ihnen festgelegten Benutzernamen und das Passwort für das Dashboard an.
Öffnen Sie Port 8000 nicht im öffentlichen Internet
Kong verwendet auf Port 8000 unverschlüsseltes HTTP. Jeder API-Schlüssel und jedes Benutzerpasswort wird im Klartext über das Netzwerk übertragen. Die Zugangsdaten für Studio verwenden außerdem Basic Authentication, also Base64-Kodierung statt Verschlüsselung.
Schalten Sie einen Reverse Proxy davor, terminieren Sie dort TLS (Transport Layer Security) und binden Sie Kong an die Loopback-Adresse, damit kein anderer Prozess darauf zugreifen kann. In docker-compose.yml wird das Port-Mapping kong zu 127.0.0.1:8000:8000, und der Proxy leitet Anfragen dorthin weiter. Traefik vor mehreren Compose-Anwendungen behandelt die Zertifikatsverwaltung. Dieser Proxy steht letztlich vor allen anderen Anwendungen auf dem Server – von diesem Stack bis zu etwas so Unkritischem wie einer als Videothek der 90er-Jahre neu aufgebauten Jellyfin-Bibliothek. Jede dieser Anwendungen benötigt einen Hostnamen statt eines weiteren offenen Ports. Wenn Sie ein Dashboard ausschließlich selbst verwenden, verzichten Sie auf den Proxy und greifen stattdessen über einen SSH-Tunnel auf den Loopback-Port zu. Diesen Ansatz verwendet auch self-hosted open-kritt, um seine Scan-Oberfläche vollständig aus dem öffentlichen Internet herauszuhalten.
Sperren Sie auch die übrigen Ports in der Firewall. Docker veröffentlicht Ports, indem es eigene iptables-Regeln schreibt, die eine naive ufw-Konfiguration nicht berücksichtigt. Diese Falle wird in warum Docker-Container Ihre ufw-Regeln ignorieren erklärt.
Datenbank sichern, nicht das Verzeichnis
Die Postgres-Daten liegen in einem Bind-Mount unter ./volumes/db/data. Wenn Sie dieses Verzeichnis kopieren, während der Container läuft, erhalten Sie eine inkonsistente Kopie. Postgres puffert Schreibvorgänge, und die Dateien auf dem Datenträger sind erst nach einem Checkpoint konsistent. Eine Wiederherstellung funktioniert in der Regel. Dabei können jedoch unbemerkt die letzten Transaktionen verloren gehen. Das ist der schlimmstmögliche Fehlerfall bei einem Backup.
Erstellen Sie stattdessen einen Dump. pg_dumpall wird im Container ausgeführt und erzeugt einen konsistenten Snapshot:
docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sqlPrüfen Sie, dass die Datei nicht leer ist, bevor Sie ihr vertrauen. Übertragen Sie diese Dumps anschließend regelmäßig vom Server auf ein anderes System. Dafür sind verschlüsselte Offsite-Backups mit restic vorgesehen. Ein geplanter Dump, der unbemerkt fehlschlägt, ist von einem vollständig fehlenden Backup nicht zu unterscheiden. Lassen Sie den Cron- oder systemd-Job deshalb eine Warnung an Ihr Telefon senden, wenn er mit einem Exit-Code ungleich null beendet wird. Sichern Sie gleichzeitig Ihr .env. Wenn JWT_SECRET verloren geht, wird jedes ausgestellte Token ungültig und jedes gespeicherte verschlüsselte Geheimnis unlesbar.
Hochgeladene Dateien liegen unter ./volumes/storage. Dabei handelt es sich um gewöhnliche Dateien, daher reicht eine einfache Kopie aus.
Aktualisierung ohne Datenverlust
Supabase legt die Image-Versionen in docker-compose.yml fest. Dadurch ändert sich nichts, bis Sie die Version selbst aktualisieren. Diese Festlegung lohnt sich für jeden Stack, den Sie manuell zusammenstellen. Deshalb legt auch ein selbst gehostetes RustDesk-Relay seine beiden Server-Images fest, statt einem veränderlichen Tag zu folgen. Ein Upgrade sollte zu einem Zeitpunkt erfolgen, den Sie bewusst wählen und für den Sie ausreichend Zeit haben. Erstellen Sie vorher jedes Mal einen Dump.
docker compose pull
sh run.sh recreaterecreate stoppt den Stack und startet ihn mit den neuen Images erneut. Ihre Daten bleiben erhalten, weil sie in den Bind-Mounts auf dem Host liegen und nicht innerhalb der Container. Lesen Sie CHANGELOG.md im Repository, bevor Sie auf eine neue Major-Version wechseln. Major-Upgrades von Postgres erfolgen nicht automatisch und erfordern ein Dump sowie eine Wiederherstellung.
Um Änderungen an der Compose-Datei zu übernehmen, klonen Sie das Upstream-Repository erneut und kopieren Sie dessen Verzeichnis docker in Ihr Projekt. Achten Sie darauf, .env nicht zu überschreiben.
Der vollständige Reset ist ein separates Skript. Er löscht alles einschließlich der Datenbank und fordert eine Bestätigung an:
sh reset.shFAQ
Warum geben meine API-Aufrufe „Invalid authentication credentials“ zurück?
Ihr ANON_KEY oder SERVICE_ROLE_KEY wurde nicht mit dem JWT_SECRET signiert, das derzeit in .env hinterlegt ist. Das Gateway prüft die Signatur jeder Anfrage und lehnt eine Abweichung ab. Erzeugen Sie alle drei Werte gemeinsam mit sh utils/generate-keys.sh --update-env neu. Führen Sie anschließend sh run.sh recreate aus, damit die Dienste die neuen Werte einlesen.
Kann ich selbst gehostetes Supabase auf einem VPS mit 2 GB ausführen?
Nicht zuverlässig. Der Stack benötigt seit Juli 2026 im Leerlauf etwa 3 GB, weil er ungefähr vierzehn Dienste ausführt. Auf einem System mit 2 GB beendet der Out-of-Memory-Killer daher Container, und in docker compose ps wird der Exit-Code 137 angezeigt. Verwenden Sie für den Produktivbetrieb 8 GB. Für die Einzelentwicklung sollten 4 GB die Untergrenze sein.
Enthält selbst gehostetes Supabase Edge Functions?
Ja. Die Compose-Datei enthält die auf Deno basierende Functions-Laufzeit. Sie führt alles aus, was Sie unter ./volumes/functions ablegen. Das globale Deployment-Netzwerk der gehosteten Plattform ist nicht enthalten. Ihre Functions laufen daher auf Ihrem einzelnen Server an einem einzigen Standort.
Wie verbinde ich mich direkt mit der Postgres-Datenbank?
Verwenden Sie docker exec -it supabase-db psql -U postgres für eine interaktive Shell direkt auf dem Server. Für einen externen Client stellen Sie über Supavisor auf Port 5432 eine Verbindung mit dem Benutzer postgres.<POOLER_TENANT_ID> und Ihrem POSTGRES_PASSWORD her. Öffnen Sie diesen Port nicht für das Internet. Greifen Sie stattdessen über ein VPN oder einen SSH-Tunnel darauf zu.
Warum verweisen meine Bestätigungs-E-Mails für die Authentifizierung auf localhost?
SITE_URL und API_EXTERNAL_URL in .env waren auf ihren Standardwerten belassen worden. Der Auth-Dienst erstellt jeden Link zur Bestätigung und zum Zurücksetzen des Passworts aus diesen beiden Werten. Deshalb versendet er die Adresse, die dort hinterlegt ist. Setzen Sie beide Werte auf Ihre tatsächliche öffentliche URL und erstellen Sie den Stack neu.