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 mitopenssl rand -hex 32JWT_SECRET— 64 Zeichen, ebenfalls peropenssl randANON_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 registrierenADDITIONAL_REDIRECT_URLS— Whitelist aller erlaubten Auth-Callback-URLsJWT_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
- Vergessene Storage-Backups — pg_dump sichert nur die DB, nicht die Files in MinIO.
- Default-JWT-Keys — öffentlich bekannt, sofort kompromittiert. Immer rotieren.
- 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.
- Time-Drift zwischen Containern — JWT-Validation bricht. NTP auf dem Host aktivieren.
shm_sizezu 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
- Mein Tech-Stack-Vergleich Supabase vs. Firebase vs. Eigenbau — folgt
- DevOps-Pipeline mit Coolify — folgt
Bei Fragen oder konkretem Bedarf für ein selbstgehostetes Supabase-Setup: einfach anfragen.
In Praxis
Verwandte Artikel
- DevOps· 3 gemeinsame TagsCoolify als Heroku-Replacement — Self-Hosted PaaS in der Praxis
- DevOps· 3 gemeinsame TagsDevOps-Pipeline mit Coolify, Next.js & GitHub Actions — Auto-Deploy ohne Vendor-Lockin
- Fullstack· 1 gemeinsame TagsNext.js 16 + Supabase Auth — der saubere SSR-Flow ohne flackernde Login-States
- KI / AIpgvector als RAG-Backbone — wann es reicht und wann du eine dedizierte Vector-DB brauchst