Technische Schritte zum Ausstieg aus Firebase und Wechsel zu 5-Dollar-Self-Hosting
TuBrief 편집팀
2026년 7월 14일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Firebase ist anfangs süß. Man muss sich keine Gedanken über die Infrastruktur machen und kann sich ganz auf die Implementierung von Funktionen konzentrieren. Doch sobald der Dienst auch nur ein wenig wächst, beginnen die Beträge auf der Rechnung zu steigen. Ein Wechsel zum Self-Hosting wirkt jedoch oft einschüchternd. Was, wenn die Daten verloren gehen? Was, wenn der Dienst bei jeder Bereitstellung stillsteht?
Das sind berechtigte Sorgen. Aber es gibt einen Weg. Ich habe den konkreten Prozess zusammengestellt, wie man Firestore-Daten sicher zu PocketBase – einem All-in-One-Backend auf SQLite-Basis – migriert und eine Umgebung für Zero-Downtime-Deployments mit GitHub Actions und Nginx aufbaut.
Das erste Hindernis beim Übertragen unstrukturierter Daten aus Firestore in ein PocketBase-Schema auf Basis eines relationalen Modells ist die Länge der Identifikatoren. Firestore verwendet 20-stellige Dokument-IDs, während PocketBase standardmäßig eine Beschränkung auf 15 alphanumerische Zeichen hat. Ignoriert man diese Länge und überträgt die Daten einfach, erhält man den Fehler validation_length_invalid.
Um dieses Problem zu umgehen, muss die Länge unter Beibehaltung der Eindeutigkeit reduziert werden. Der sicherste Weg ist, die Firestore-ID mit SHA256 zu hashen und dann die ersten 15 Zeichen abzuschneiden. Sorgen um Kollisionen sind unbegründet: Selbst bei großen Datensätzen ist die Wahrscheinlichkeit, dass sich 15-stellige Hash-Werte überschneiden, so gering, dass sie vernachlässigt werden kann.
Die Struktur eines Node.js-Migrationsskripts, das dies verarbeitet, sieht wie folgt aus. Dabei werden die Daten mithilfe der Pakete firebase-admin und axios in 100er-Blöcken abgerufen.
`javascript
// migration.js
const admin = require('firebase-admin');
const axios = require('axios');
const crypto = require('crypto');
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
const db = admin.firestore();
const PB_URL = 'http://127.0.0.1:8090/api/collections/posts/records';
async function migrate() {
let lastDoc = null;
let hasMore = true;
while (hasMore) {
let query = db.collection('posts').orderBy('name').limit(100);
if (lastDoc) {
query = query.startAfter(lastDoc);
}
const snapshot = await query.get();
if (snapshot.empty) {
hasMore = false;
break;
}
for (const doc of snapshot.docs) {
const data = doc.data();
// Konvertierung der 20-stelligen Firestore-ID in 15 alphanumerische Zeichen
const newId = crypto.createHash('sha256').update(doc.id).digest('hex').substring(0, 15);
try {
await axios.post(PB_URL, {
id: newId,
firestore_id: doc.id, // Speichern der Original-ID zur Validierung
title: data.title,
content: data.content
});
} catch (err) {
console.error(`Migration fehlgeschlagen: ${doc.id}`, err.response?.data);
}
}
lastDoc = snapshot.docs[snapshot.docs.length - 1];
}
}
migrate();
`
Um diesen Vorgang ohne Unterbrechungen durchzuführen, sollte man eine Übergangsphase einplanen. Verschieben Sie zunächst über 90 % der bestehenden Daten im Hintergrund mit diesem Skript. Implementieren Sie dann in Ihrem Client-Code ein Dual-Write-Verfahren, bei dem Schreibvorgänge gleichzeitig sowohl an Firebase als auch an PocketBase gesendet werden. Sobald sichergestellt ist, dass die Daten auf beiden Seiten übereinstimmen, können Sie die Lese-Endpunkte auf PocketBase umstellen und den Firebase-Code entfernen. Für den Benutzer wird der Server nicht eine einzige Sekunde ausfallen.
PocketBase ist ein leichtgewichtiges Backend, das als einzelne Binärdatei ausgeführt wird. Wenn man es jedoch einfach nur auf einem Linux-Server startet, gibt es keine Möglichkeit, den Dienst wiederherzustellen, sollte der Prozess aufgrund eines unerwarteten Speichermangels (OOM) beendet werden.
Sie müssen systemd konfigurieren, damit der Prozess bei einem Absturz automatisch neu gestartet wird. Erstellen Sie eine Datei unter /etc/systemd/system/pocketbase.service und tragen Sie den folgenden Inhalt ein:
`ini
[Unit]
Description=PocketBase Service
After=network.target
[Service]
Type=simple
User=pocketbase
Group=pocketbase
LimitNOFILE=65535
ExecStart=/opt/pocketbase/pocketbase serve --http="127.0.0.1:8090"
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
`
Hier ist die Einstellung LimitNOFILE=65535 wichtig. Sie verhindert, dass bei hoher gleichzeitiger Nutzung der Echtzeit-Abonnements (WebSockets) die Verbindung aufgrund der Standard-Dateideskriptor-Grenzen von Linux unterbrochen wird.
An der Vorderseite verwenden wir Nginx für SSL-Zertifikate und als Proxy. Fügen Sie der Konfiguration /etc/nginx/sites-available/pocketbase Optionen hinzu, damit Echtzeit-Stream-Verbindungen nicht verzögert werden:
`nginx
upstream pocketbase {
server 127.0.0.1:8090;
}
server {
server_name api.yourdomain.com;
location / {
proxy_pass http://pocketbase;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Unterstützung für WebSockets und SSE-Echtzeit-Streaming
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
}
}
`
Sobald Sie nun mit dem Befehl certbot --nginx ein HTTPS-Zertifikat hinzufügen, ist die grundlegende Infrastruktur fertig.
Erfahrungsgemäß können selbst die günstigsten VPS mit 512 MB RAM mehr als 1.000 leichte Anfragen pro Sekunde problemlos verarbeiten, wenn man die SQLite-Einstellungen leicht optimiert. Um die Leistung von SQLite beim Betrieb von PocketBase zu maximieren, sollte der WAL-Modus (Write-Ahead Log) aktiviert sein. PocketBase aktiviert diesen standardmäßig, aber falls Sie SQLite direkt bearbeiten, sollten Sie die Einstellungen PRAGMA journal_mode=WAL; und PRAGMA synchronous=NORMAL; überprüfen. Dadurch entfallen fast alle Engpässe, bei denen Anfragen warten müssen, während auf die Festplatte geschrieben wird.
Der häufigste Fehler, den Indie-Hacker machen, ist das einfache Kopieren der SQLite-Datenbankdatei (data.db) von einem laufenden Server zu Sicherungszwecken. Dabei besteht ein hohes Risiko, dass die Daten während des Backups beschädigt werden.
Wir verwenden stattdessen Litestream. Es ist ein Tool, das die WAL-Frames von SQLite in Echtzeit abfängt und nur die geänderten Teile an den Cloud-Speicher sendet. Als Backup-Speicher empfehle ich Cloudflare R2, da hier keine Kosten für ausgehenden Datentransfer anfallen und Backups daher nahezu kostenlos sind.
Erstellen Sie eine Konfigurationsdatei unter /etc/litestream.yml wie folgt:
`yaml
dbs:
`
Mit dieser Einstellung können Sie selbst bei Verlust der ursprünglichen DB-Datei mit einem einzigen Befehl den Zustand bis auf die letzte Sekunde genau wiederherstellen:
bash litestream restore -if-replica-exists /opt/pocketbase/pb_data/data.db
Falls Sie während der Entwicklung versehentlich Daten löschen und zu einem bestimmten Zeitpunkt zurückkehren müssen, ist auch das möglich:
`bash
sudo systemctl stop pocketbase.service
litestream restore -timestamp "2026-07-14T15:00:00Z" -o /tmp/recovered.db /opt/pocketbase/pb_data/data.db
sqlite3 /tmp/recovered.db "PRAGMA integrity_check;"
mv /tmp/recovered.db /opt/pocketbase/pb_data/data.db
chown -R pocketbase:pocketbase /opt/pocketbase/pb_data/
sudo systemctl start pocketbase.service
`
Cloudflare R2 bietet monatlich 10 GB kostenlosen Speicherplatz. Dazu kommen kostenlose Kontingente für 1 Million Schreib- und 10 Millionen Lesezugriffe, sodass die Backup-Kosten für einen Einzel-Dienst nahezu bei Null liegen.
Es ist bequem, mit Docker zu deployen, aber auf kleinen VPS mit 512 MB oder 1 GB RAM ist der Speicher, den der Docker-Daemon selbst verbraucht, bereits kostbar. Wir implementieren ein Zero-Downtime-Deployment ohne Docker durch das wechselnde Umschalten von Ports im Linux systemd (Blue-Green Deployment).
Wir nutzen die Eigenschaft von SQLite, dass mehrere Prozesse eine einzelne Datenbankdatei lesen und schreiben können. Wir registrieren eine systemd-Dienstvorlage, mit der die PocketBase-Binärdatei jeweils auf Port 9011 (Blau) und Port 9012 (Grün) gestartet werden kann.
Hier ist ein Beispiel für einen Workflow, der eine mit GitHub Actions erstellte leichtgewichtige Binärdatei auf den Server überträgt:
`yaml
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o pocketbase main.go
- name: Transfer Binary to VPS
uses: appleboy/scp-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
source: "pocketbase"
target: "/srv/pocketbase/next_release"
`
Sobald die Binärdatei auf dem Server ankommt, wird das Deployment-Skript ausgeführt. Es prüft, ob derzeit Port 9011 oder 9012 aktiv ist, und startet die neue Version der Binärdatei auf dem freien Port:
`bash
#!/bin/bash
CURRENT_PORT=$(curl -s http://127.0.0.1:8090/api/health | jq -r '.port' 2>/dev/null || echo "9011")
if [ "$CURRENT_PORT" = "9011" ]; then
TARGET_PORT="9012"
else
TARGET_PORT="9011"
fi
sudo systemctl start pocketbase@$TARGET_PORT.service
sleep 3
HEALTH_CHECK=TARGET_PORT/api/health | jq -r '.status')
if [ "HEALTH_CHECK" = "OK" ]; then
# Nginx Upstream-Einstellung auf den neuen Port anpassen und neu laden
echo "upstream pocketbase { server 127.0.0.1:TARGET_PORT; }" | sudo tee /etc/nginx/conf.d/upstream.conf
sudo systemctl reload nginx
# Prozess der alten Version beenden
sudo systemctl stop pocketbase@$CURRENT_PORT.service
echo "Deployment abgeschlossen. Umschaltung auf Port TARGET_PORT.service
exit 1
fi
`
Mit dieser Methode funktioniert ein Zero-Downtime-Deployment perfekt, ohne auf teure Container-Orchestrierungstools angewiesen zu sein. Selbst wenn ein Benutzer während des Deployments eine Anfrage sendet, hält Nginx diese für den Bruchteil einer Sekunde (im Millisekundenbereich) während des Neuladens an und leitet sie dann sicher an den neuen Port weiter.
Self-Hosting erfordert bei der ersten Einrichtung zwar etwas Arbeit, aber sobald die Konfiguration steht, ist es eine hervorragende Grundlage, auf der man sich ohne Sorgen um monatlich anfallende Infrastrukturkosten voll und ganz auf das Produkt konzentrieren kann.