Self-Hosted Supabase mit Docker — Setup, Backups & Production-Härtung

Komplettes Setup für ein produktionsreifes, selbstgehostetes Supabase auf einem Single-VPS: Docker-Compose-Stack, Auth-Härtung, automatische Backups, Storage über MinIO und Monitoring. Inklusive der Fallstricke, die in der offiziellen Doku fehlen.

19. Mai 20269 min Lesezeit730 Wörter

Warum Supabase selbst hosten?

Managed Supabase ist großartig zum Prototyping, aber sobald Datenschutz, Kostenkontrolle oder Compliance-Anforderungen ins Spiel kommen, lohnt der Schritt zum Self-Hosting:

  • Volle Datensouveränität — DSGVO/GDPR-konform, keine US-Cloud-Provider, deine Kunden-Daten liegen auf deiner Hardware.
  • Kostendeckelung — ein 20 €/Monat VPS reicht für die meisten Side-Projects, kein Per-Row-Pricing.
  • Erweiterbarkeit — Custom Postgres-Extensions, eigene Edge-Functions, Storage-Backend wählbar.

Der Nachteil: du musst Backups, Monitoring, Updates und Auth-Härtung selbst verantworten. Dieser Artikel zeigt das vollständige Setup, das ich auf tobiasjonas-ludwig.de selbst nutze.

Architektur-Überblick

┌─────────────────┐    ┌──────────────────┐
│ Coolify Frontend│───▶│ Supabase Backend │
│ (Next.js Build) │    │ (Docker-Compose) │
└─────────────────┘    │  ┌────────────┐  │
                       │  │ PostgreSQL │  │
                       │  │ + GoTrue   │  │
                       │  │ + Realtime │  │
                       │  │ + Kong API │  │
                       │  │ + Studio   │  │
                       │  │ + MinIO    │  │
                       │  └────────────┘  │
                       └──────────────────┘

Zwei VPS sind die saubere Trennung: Frontend (Coolify) und Backend (Supabase Docker-Stack). Vorteil: Frontend-Rebuilds blockieren nicht die DB, und Backups lassen sich isoliert ziehen.

Schritt 1: Docker-Compose & .env

Klone das offizielle Supabase Docker-Repo:

git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
cp .env.example .env

Kritische Variablen in .env (NIE Defaults belassen):

  • POSTGRES_PASSWORD — mindestens 32 Zeichen, generiert mit openssl rand -hex 32
  • JWT_SECRET — 64 Zeichen, ebenfalls per openssl rand
  • ANON_KEY / SERVICE_ROLE_KEY — über das offizielle JWT-Tool generieren, NICHT die Defaults nutzen (öffentlich bekannt = sofort kompromittiert)
  • SITE_URL — deine Produktiv-Domain (https://tobiasjonas-ludwig.de)
  • SMTP_* — externer SMTP (z. B. SendGrid), sonst kommen Auth-Mails nicht durch

Start:

docker compose -f docker-compose.yml --env-file .env up -d

Schritt 2: Reverse-Proxy & TLS

Vor Kong (Port 8000) gehört ein Caddy oder Traefik für TLS-Terminierung:

backend.deine-domain.de {
  reverse_proxy localhost:8000
}

Caddy holt automatisch ein Let's-Encrypt-Zertifikat. Nicht vergessen: in der .env auch API_EXTERNAL_URL=https://backend.deine-domain.de setzen, sonst stimmen die generierten Auth-Redirect-URLs nicht.

Schritt 3: Auth-Härtung

Per Default ist Sign-Up offen. Für ein Single-Admin- oder Eingeladene-User-Setup folgendes setzen:

  • ENABLE_EMAIL_SIGNUP=false — niemand kann sich frei registrieren
  • ADDITIONAL_REDIRECT_URLS — Whitelist aller erlaubten Auth-Callback-URLs
  • JWT_EXPIRY=3600 — kurze Token-Lebensdauer, ergänzt um Refresh-Tokens
  • In der Postgres-DB alle Tabellen mit RLS schützen (ALTER TABLE ... ENABLE ROW LEVEL SECURITY)

Admin-User-Creation läuft dann per SQL-Insert in auth.users durch den Service-Role-Client, nicht über die öffentliche API.

Schritt 4: Automatische Backups

Der häufigste Fehler: keine externen Backups. Snapshot des Docker-Volumes reicht nicht, wenn der Server ausfällt. Mein Setup:

#!/usr/bin/env bash
# /opt/backup-supabase.sh — cron daily at 02:00
set -euo pipefail

TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR=/var/backups/supabase
mkdir -p "$BACKUP_DIR"

# PostgreSQL Dump
docker exec supabase-db pg_dump -U postgres -d postgres \
  --no-owner --no-privileges --clean --if-exists \
  | gzip > "$BACKUP_DIR/db-$TIMESTAMP.sql.gz"

# MinIO Storage (rclone konfiguriert auf B2/S3)
rclone sync /var/lib/docker/volumes/supabase_storage/_data \
  remote:supabase-storage-backup --transfers=4

# Cleanup älter als 30 Tage
find "$BACKUP_DIR" -name "db-*.sql.gz" -mtime +30 -delete

Cron-Eintrag: 0 2 * * * /opt/backup-supabase.sh

Wichtig: Restore mindestens einmal vollständig testen, bevor das Live geht. Ein nicht-getestetes Backup ist kein Backup.

Schritt 5: Monitoring & Alerting

Minimal-Setup mit Uptime-Kuma (Docker-Container, Web-UI) auf einem dritten Host:

  • HTTP-Check auf https://backend.deine-domain.de/rest/v1/ — alle 60 s
  • TCP-Check auf Postgres-Port (sofern extern erreichbar)
  • Cert-Expiry-Check auf das TLS-Zertifikat
  • Notification per ntfy.sh oder Telegram

Postgres-interne Metriken über pg_stat_statements ansehen — langsame Queries früh erkennen.

Fallstricke aus der Praxis

  1. Vergessene Storage-Backups — pg_dump sichert nur die DB, nicht die Files in MinIO.
  2. Default-JWT-Keys — öffentlich bekannt, sofort kompromittiert. Immer rotieren.
  3. Kein Health-Check für GoTrue — wenn der Auth-Service stirbt, läuft die DB weiter und niemand merkt's, bis ein Nutzer sich nicht mehr einloggen kann.
  4. Time-Drift zwischen Containern — JWT-Validation bricht. NTP auf dem Host aktivieren.
  5. shm_size zu klein — bei vielen parallelen Queries crasht Postgres ohne klare Fehlermeldung.

Wann nicht selbst hosten?

Self-Hosting lohnt sich nicht, wenn:

  • Du noch im Prototyp-Stadium bist und Setup-Zeit teurer ist als Cloud-Kosten
  • Du hochelastische Lastspitzen hast (>10x Tag/Nacht-Unterschied)
  • Du keinen Bereitschaftsdienst für 3-Uhr-nachts-Alerts hast
  • Compliance-Audits einen zertifizierten Provider fordern

Für stabile Mid-Size-Projekte (10–500 aktive Nutzer, planbare Last) ist Self-Hosting mein Default — niedrigere TCO und volle Kontrolle. Für alles darüber hinaus oder hochskaliert: Supabase Cloud Pro oder ein Managed-Postgres (Neon, RDS).

Nächste Schritte

Bei Fragen oder konkretem Bedarf für ein selbstgehostetes Supabase-Setup: einfach anfragen.

In Praxis

Verwandte Artikel

Neue Artikel via RSS abonnieren