Anleitung: Worauf beim Umzug auf einen eigenständigen Server zu achten ist
Bestandsaufnahme per SSH auf 192.168.1.222 (2026-08-22): 4-Kern-NAS (i5-6500), 15 GB RAM, Disk 86 % voll (32 GB frei), ~45 Container von anderen Projekten (Immich, Paperless, Gitea selbst, GlitchTip, pflege/gon/stllager, …) teilen sich die Kerne mit den 3 PawFeed-Replicas. Das war schon der Auslöser für die Replica-Skalierung (#26) — ein eigener Server für PawFeed löst das grundsätzlicher.
Die gute Nachricht zuerst: fast der ganze Zustand liegt schon extern
PawFeed selbst ist praktisch zustandslos. Auf dem NAS liegen nur:
Alles andere ist bereits Cloud/extern und muss nicht migriert werden, solange die ENV-Werte gleich bleiben:
Postgres-DB → Supabase Cloud (nicht auf dem NAS)
Datei-/Avatar-Storage → Supabase Storage
Video → Mux
Auth → Clerk
Zahlungen → Stripe (LIVE-Mode!)
Das bedeutet: keine DB-Migration, keine Storage-Kopie, keine Downtime-kritische Datenwanderung für den größten Teil des Systems.
Der eine Punkt, der wirklich Denkarbeit braucht: Netzwerk-Kopplung an NPM
Aktuell hängen NPM (Nginx Proxy Manager) und die PawFeed-Container im selben Docker-Bridge-Netzwerk (nginx_default) auf derselben Maschine — NPM erreicht die Replicas per Container-DNS-Name (pawfeed-1:3000 usw., siehe docker/npm-upstream.conf). Sobald PawFeed auf anderer physischer Hardware läuft, existiert dieses gemeinsame Docker-Netzwerk nicht mehr — NPM kann die Container nicht mehr per Docker-DNS erreichen.
Zwei Optionen:
Option A (empfohlen): NPM bleibt der zentrale Reverse Proxy auf dem alten NAS.
Kein DNS-Wechsel nötig (Cloudflare zeigt weiter auf dieselbe DDNS-Adresse/öffentliche IP desselben Routers)
Änderung: docker/npm-upstream.conf-Äquivalent (/data/nginx/custom/http_top.conf auf dem NPM-Volume) bekommt statt Docker-DNS-Namen die LAN-IP des neuen Servers + gemappte Ports, z. B.:
Voraussetzung: docker-compose.yml auf dem neuen Server muss die 3 Replica-Ports auf den Host mappen (ports: "3001:3000" etc.) — aktuell macht das compose-File das nicht, weil es sich auf das gemeinsame Docker-Netzwerk verlässt.
Nachteil: alter NAS bleibt Single Point of Failure für den TLS-Terminierungspunkt.
Option B: Eigenständiges NGINX (oder Caddy/Traefik) direkt auf dem neuen Server.
Voll unabhängig vom alten NAS, eigenes Let's-Encrypt-Zertifikat für pawfeed.org
Erfordert: Router-Portweiterleitung 80/443 von der alten NAS-LAN-IP auf die neue Server-LAN-IP umstellen
Mehr Aufwand (neues Zertifikat, neue Firewall-Regeln), aber sauberer Schnitt — keine Restabhängigkeit vom NAS mehr für PawFeed.
Empfehlung: mit Option A starten (kleinerer Blast-Radius, reversibel), Option B als Folgeschritt, sobald der neue Server sich bewährt hat.
Checkliste nach Bereich
DNS / Domain
Nichts zu tun, falls Option A gewählt wird — pawfeed.org zeigt weiterhin auf dieselbe DDNS-Adresse/denselben Router.
Bei Option B: Router-Portforwarding 80/443 auf neue Server-IP umstellen, neues Zertifikat ausstellen (Certbot/acme.sh), Cloudflare-Einträge bleiben unverändert (Domain, nicht Server, wird aufgelöst).
Secrets / .env
docker/.env (15 Variablen: Supabase, Clerk, Stripe, Mux, Sentry/GlitchTip, SMTP, ADMIN_SECRET, ADMIN_OWNER_ID, CRON_SECRET, REDIS_PASSWORD) sicher auf den neuen Server übertragen — nicht per Git/FTP, sondern scp über SSH oder Passwort-Manager. Werte bleiben identisch (App-Ebene, nicht Server-Ebene) — kein Rotieren nötig, außer man will es bewusst.
Nach Transfer: temporäre Kopien löschen, chmod 600 docker/.env auf dem neuen Server.
Feed-Sorted-Sets (src/lib/feed-helpers.ts) sind der einzige Redis-Inhalt mit echtem UX-Wert — es gibt keinen Backfill-Mechanismus, der sie aus Postgres neu aufbaut (Feed wird nur beim fanOutPost bei neuen Posts befüllt). Bei einem Kaltstart auf leerem Redis sehen alle Nutzer beim Cutover kurzzeitig leere Feeds, bis neue Posts reinkommen.
Empfehlung: Docker-Volume redis_data klonen statt neu zu starten — sauberster Weg:
# auf dem alten NAS: Redis kurz stoppen (Konsistenz), Volume sichern
docker stop pawfeed-redis
docker run --rm -v pawfeed_redis_data:/from -v /tmp:/to alpine \
tar czf /to/redis-data.tar.gz -C /from .
scp /tmp/redis-data.tar.gz <neuer-server>:/tmp/
# auf dem neuen Server: Volume vorab anlegen, entpacken
docker volume create pawfeed_redis_data
docker run --rm -v pawfeed_redis_data:/to -v /tmp:/from alpine \
tar xzf /from/redis-data.tar.gz -C /to
Downtime-Fenster dafür ist klein (Redis-Stop nur für den tar-Export), passt gut ins Cutover-Wartungsfenster.
Datenbank / Storage / Video / Auth / Zahlungen
Kein Handlungsbedarf — alles extern (Supabase, Mux, Clerk, Stripe). Läuft vom neuen Server aus identisch, solange die ENV-Werte stimmen und der neue Server Internetzugang hat.
Supabase-Connection-Pool-Limit im Auge behalten: aktuell DB_POOL_MAX=10 × 3 Replicas = 30 Verbindungen, weit unter dem Supabase-Limit (200). Bleibt unverändert, unabhängig vom Server.
Webhooks (Mux, Stripe, Clerk)
Zeigen alle auf https://pawfeed.org/api/webhooks/... — domain-basiert, nicht server-basiert. Bei Option A (Domain bleibt gleich) ist hier nichts zu tun. Bei Option B ebenfalls nichts zu tun, solange die Domain gleich bleibt (nur das Zertifikat/der Terminierungspunkt wechselt).
Monitoring (GlitchTip)
GlitchTip läuft selbst-gehostet auf dem alten NAS (glitchtip-web, -worker, -postgres, -redis), komplett unabhängig vom PawFeed-Compose-Stack, erreichbar über glitchtip.pawfeed.org.
Entscheidung nötig: GlitchTip auf dem alten NAS belassen (PawFeed auf dem neuen Server sendet Events einfach per HTTPS über die öffentliche Domain dorthin — funktioniert ohne Änderung) oder ebenfalls auf den neuen Server umziehen. Für den ersten Schritt: auf dem alten NAS belassen, spätere Konsolidierung ist ein separates Thema.
Cron-Jobs
3 Einträge in daniels Crontab auf dem alten NAS: trim-feeds (4 Uhr), anniversaries (8 Uhr), backup-db.sh (3 Uhr). Müssen auf dem neuen Server neu angelegt werden (crontab -e, Skripte aus docker/ mitnehmen).
Akuter Fund, unabhängig vom Umzug:run-cron.sh ruft aktuell http://localhost:4563 — diesen Host-Port gibt es seit der 3-Replica-Umstellung (#26, 2026-08-16) nicht mehr (kein Port-Mapping mehr im compose-File). Cron-Log zeigt seit 2026-08-18 durchgehend HTTP 000 — trim-feeds und anniversaries laufen seit 4 Tagen ins Leere. Sollte so oder so (auch ohne Umzug) auf https://pawfeed.org/api/cron/... umgestellt werden. Beim Server-Umzug gleich mit erledigen.
backup-db.sh (täglicher pg_dump der Supabase-DB, 14 Tage Retention) läuft unabhängig vom App-Server — kann 1:1 auf den neuen Server umziehen, sobald das DIRECT_URL-Secret dort liegt.
Hardware-Dimensionierung
Aktueller Engpass war reine CPU-Kontention mit ~45 fremden Containern auf 4 Kernen (siehe #26-Lasttest: harte Decke bei ~60–65 Req/Sek). Ein eigenständiger Server sollte mindestens 4, besser 6–8 Kerne exklusiv für PawFeed bereitstellen (Puffer für eine 4. oder 5. Replica, wenn's weiter wächst).
RAM: 3 Node-Prozesse + Redis + Docker-Build-Overhead — mit 8 GB dediziert komfortabel, 16 GB falls später mehr Replicas dazukommen.
Disk: reichlich Marge einplanen (alter NAS steht bei 86 % voll — das war nie PawFeed selbst, PawFeed-Verzeichnis ist nur ~1,5 GB, aber es zeigt, wie eng geteilte Hardware werden kann).
Docker ≥ 29.x / Compose ≥ v5.x auf dem neuen Server installieren (aktuelle Versionen auf dem NAS, zum Vergleich).
Deploy-Workflow
docker/rolling-deploy.sh, start.sh, docker-compose.yml unverändert vom Repo übernehmen (ggf. ports:-Mapping ergänzen, siehe Option A oben).
SSH-Key-Zugriff auf Gitea (ssh://git@192.168.1.222:2222/admin/petfeed.git) für den neuen Server einrichten, falls Deploys künftig per git pull statt scp laufen sollen.
## Anleitung: Worauf beim Umzug auf einen eigenständigen Server zu achten ist
Bestandsaufnahme per SSH auf `192.168.1.222` (2026-08-22): 4-Kern-NAS (i5-6500), 15 GB RAM, Disk **86 % voll** (32 GB frei), ~45 Container von anderen Projekten (Immich, Paperless, Gitea selbst, GlitchTip, pflege/gon/stllager, …) teilen sich die Kerne mit den 3 PawFeed-Replicas. Das war schon der Auslöser für die Replica-Skalierung (#26) — ein eigener Server für PawFeed löst das grundsätzlicher.
### Die gute Nachricht zuerst: fast der ganze Zustand liegt schon extern
PawFeed selbst ist praktisch zustandslos. Auf dem NAS liegen nur:
- 3× `pawfeed-N`-Container (Next.js Standalone, `node server.js`)
- 1× `pawfeed-redis` (Feed-Cache, Rate-Limits, Wartungsmodus-Flag, DAU/MAU-Zählung)
Alles andere ist bereits Cloud/extern und muss **nicht** migriert werden, solange die ENV-Werte gleich bleiben:
- **Postgres-DB** → Supabase Cloud (nicht auf dem NAS)
- **Datei-/Avatar-Storage** → Supabase Storage
- **Video** → Mux
- **Auth** → Clerk
- **Zahlungen** → Stripe (LIVE-Mode!)
Das bedeutet: keine DB-Migration, keine Storage-Kopie, keine Downtime-kritische Datenwanderung für den größten Teil des Systems.
### Der eine Punkt, der wirklich Denkarbeit braucht: Netzwerk-Kopplung an NPM
Aktuell hängen NPM (Nginx Proxy Manager) und die PawFeed-Container im **selben Docker-Bridge-Netzwerk** (`nginx_default`) auf derselben Maschine — NPM erreicht die Replicas per Container-DNS-Name (`pawfeed-1:3000` usw., siehe `docker/npm-upstream.conf`). Sobald PawFeed auf **anderer physischer Hardware** läuft, existiert dieses gemeinsame Docker-Netzwerk nicht mehr — NPM kann die Container nicht mehr per Docker-DNS erreichen.
Zwei Optionen:
**Option A (empfohlen): NPM bleibt der zentrale Reverse Proxy auf dem alten NAS.**
- Kein DNS-Wechsel nötig (Cloudflare zeigt weiter auf dieselbe DDNS-Adresse/öffentliche IP desselben Routers)
- Bestehendes Let's-Encrypt-Zertifikat bleibt gültig
- Änderung: `docker/npm-upstream.conf`-Äquivalent (`/data/nginx/custom/http_top.conf` auf dem NPM-Volume) bekommt statt Docker-DNS-Namen die **LAN-IP des neuen Servers + gemappte Ports**, z. B.:
```nginx
upstream pawfeed_upstream {
least_conn;
server 192.168.1.XXX:3001 max_fails=3 fail_timeout=30s;
server 192.168.1.XXX:3002 max_fails=3 fail_timeout=30s;
server 192.168.1.XXX:3003 max_fails=3 fail_timeout=30s;
}
```
- Voraussetzung: `docker-compose.yml` auf dem neuen Server muss die 3 Replica-Ports **auf den Host mappen** (`ports: "3001:3000"` etc.) — aktuell macht das compose-File das nicht, weil es sich auf das gemeinsame Docker-Netzwerk verlässt.
- Nachteil: alter NAS bleibt Single Point of Failure für den TLS-Terminierungspunkt.
**Option B: Eigenständiges NGINX (oder Caddy/Traefik) direkt auf dem neuen Server.**
- Voll unabhängig vom alten NAS, eigenes Let's-Encrypt-Zertifikat für `pawfeed.org`
- Erfordert: Router-Portweiterleitung 80/443 von der alten NAS-LAN-IP auf die neue Server-LAN-IP umstellen
- Mehr Aufwand (neues Zertifikat, neue Firewall-Regeln), aber sauberer Schnitt — keine Restabhängigkeit vom NAS mehr für PawFeed.
Empfehlung: mit **Option A** starten (kleinerer Blast-Radius, reversibel), **Option B** als Folgeschritt, sobald der neue Server sich bewährt hat.
### Checkliste nach Bereich
**DNS / Domain**
- [ ] Nichts zu tun, *falls* Option A gewählt wird — `pawfeed.org` zeigt weiterhin auf dieselbe DDNS-Adresse/denselben Router.
- [ ] Bei Option B: Router-Portforwarding 80/443 auf neue Server-IP umstellen, neues Zertifikat ausstellen (Certbot/acme.sh), Cloudflare-Einträge bleiben unverändert (Domain, nicht Server, wird aufgelöst).
**Secrets / `.env`**
- [ ] `docker/.env` (15 Variablen: Supabase, Clerk, Stripe, Mux, Sentry/GlitchTip, SMTP, `ADMIN_SECRET`, `ADMIN_OWNER_ID`, `CRON_SECRET`, `REDIS_PASSWORD`) sicher auf den neuen Server übertragen — **nicht per Git/FTP**, sondern `scp` über SSH oder Passwort-Manager. Werte bleiben identisch (App-Ebene, nicht Server-Ebene) — kein Rotieren nötig, außer man will es bewusst.
- [ ] Nach Transfer: temporäre Kopien löschen, `chmod 600 docker/.env` auf dem neuen Server.
**Redis-Daten**
- [ ] Rate-Limits, Wartungsmodus-Flag, DAU/MAU-Sets, Stats-Cache: alles TTL-basiert/ephemer — **unkritisch, darf leer starten.**
- [ ] Feed-Sorted-Sets (`src/lib/feed-helpers.ts`) sind der einzige Redis-Inhalt mit echtem UX-Wert — es gibt **keinen Backfill-Mechanismus**, der sie aus Postgres neu aufbaut (Feed wird nur beim `fanOutPost` bei neuen Posts befüllt). Bei einem Kaltstart auf leerem Redis sehen alle Nutzer beim Cutover kurzzeitig leere Feeds, bis neue Posts reinkommen.
- Empfehlung: **Docker-Volume `redis_data` klonen** statt neu zu starten — sauberster Weg:
```bash
# auf dem alten NAS: Redis kurz stoppen (Konsistenz), Volume sichern
docker stop pawfeed-redis
docker run --rm -v pawfeed_redis_data:/from -v /tmp:/to alpine \
tar czf /to/redis-data.tar.gz -C /from .
scp /tmp/redis-data.tar.gz <neuer-server>:/tmp/
# auf dem neuen Server: Volume vorab anlegen, entpacken
docker volume create pawfeed_redis_data
docker run --rm -v pawfeed_redis_data:/to -v /tmp:/from alpine \
tar xzf /from/redis-data.tar.gz -C /to
```
- Downtime-Fenster dafür ist klein (Redis-Stop nur für den `tar`-Export), passt gut ins Cutover-Wartungsfenster.
**Datenbank / Storage / Video / Auth / Zahlungen**
- [ ] Kein Handlungsbedarf — alles extern (Supabase, Mux, Clerk, Stripe). Läuft vom neuen Server aus identisch, solange die ENV-Werte stimmen und der neue Server Internetzugang hat.
- [ ] Supabase-Connection-Pool-Limit im Auge behalten: aktuell `DB_POOL_MAX=10` × 3 Replicas = 30 Verbindungen, weit unter dem Supabase-Limit (200). Bleibt unverändert, unabhängig vom Server.
**Webhooks (Mux, Stripe, Clerk)**
- [ ] Zeigen alle auf `https://pawfeed.org/api/webhooks/...` — **domain-basiert, nicht server-basiert.** Bei Option A (Domain bleibt gleich) ist hier nichts zu tun. Bei Option B ebenfalls nichts zu tun, solange die Domain gleich bleibt (nur das Zertifikat/der Terminierungspunkt wechselt).
**Monitoring (GlitchTip)**
- [ ] GlitchTip läuft selbst-gehostet auf dem *alten* NAS (`glitchtip-web`, `-worker`, `-postgres`, `-redis`), komplett unabhängig vom PawFeed-Compose-Stack, erreichbar über `glitchtip.pawfeed.org`.
- [ ] Entscheidung nötig: GlitchTip auf dem alten NAS belassen (PawFeed auf dem neuen Server sendet Events einfach per HTTPS über die öffentliche Domain dorthin — funktioniert ohne Änderung) **oder** ebenfalls auf den neuen Server umziehen. Für den ersten Schritt: **auf dem alten NAS belassen**, spätere Konsolidierung ist ein separates Thema.
**Cron-Jobs**
- [ ] 3 Einträge in `daniel`s Crontab auf dem alten NAS: `trim-feeds` (4 Uhr), `anniversaries` (8 Uhr), `backup-db.sh` (3 Uhr). Müssen auf dem neuen Server neu angelegt werden (`crontab -e`, Skripte aus `docker/` mitnehmen).
- [ ] **Akuter Fund, unabhängig vom Umzug:** `run-cron.sh` ruft aktuell `http://localhost:4563` — diesen Host-Port gibt es seit der 3-Replica-Umstellung (#26, 2026-08-16) nicht mehr (kein Port-Mapping mehr im compose-File). Cron-Log zeigt seit 2026-08-18 durchgehend `HTTP 000` — **`trim-feeds` und `anniversaries` laufen seit 4 Tagen ins Leere.** Sollte so oder so (auch ohne Umzug) auf `https://pawfeed.org/api/cron/...` umgestellt werden. Beim Server-Umzug gleich mit erledigen.
- [ ] `backup-db.sh` (täglicher `pg_dump` der Supabase-DB, 14 Tage Retention) läuft unabhängig vom App-Server — kann 1:1 auf den neuen Server umziehen, sobald das `DIRECT_URL`-Secret dort liegt.
**Hardware-Dimensionierung**
- [ ] Aktueller Engpass war reine CPU-Kontention mit ~45 fremden Containern auf 4 Kernen (siehe #26-Lasttest: harte Decke bei ~60–65 Req/Sek). Ein eigenständiger Server sollte **mindestens 4, besser 6–8 Kerne exklusiv für PawFeed** bereitstellen (Puffer für eine 4. oder 5. Replica, wenn's weiter wächst).
- [ ] RAM: 3 Node-Prozesse + Redis + Docker-Build-Overhead — mit 8 GB dediziert komfortabel, 16 GB falls später mehr Replicas dazukommen.
- [ ] Disk: reichlich Marge einplanen (alter NAS steht bei 86 % voll — das war nie PawFeed selbst, PawFeed-Verzeichnis ist nur ~1,5 GB, aber es zeigt, wie eng geteilte Hardware werden kann).
- [ ] Docker ≥ 29.x / Compose ≥ v5.x auf dem neuen Server installieren (aktuelle Versionen auf dem NAS, zum Vergleich).
**Deploy-Workflow**
- [ ] `docker/rolling-deploy.sh`, `start.sh`, `docker-compose.yml` unverändert vom Repo übernehmen (ggf. `ports:`-Mapping ergänzen, siehe Option A oben).
- [ ] SSH-Key-Zugriff auf Gitea (`ssh://git@192.168.1.222:2222/admin/petfeed.git`) für den neuen Server einrichten, falls Deploys künftig per `git pull` statt `scp` laufen sollen.
Voraussetzung für alle Phasen: neuer Server hat Linux, Docker, NGINX/Docker Compose installiert (wie in der Frage genannt) und ist im selben LAN wie der alte NAS erreichbar.
Phase 0 — Vorbereitung (kein Risiko für Prod)
SSH-Zugriff auf neuen Server einrichten (Key-Auth, analog zu daniel@192.168.1.222)
Docker + Compose Version prüfen (Ziel: ≥ Docker 29.x / Compose v5.x, wie auf dem NAS)
Gitea-SSH-Key des neuen Servers zu admin/petfeed hinzufügen (falls künftig git pull statt scp-Deploy)
Entscheidung treffen: Option A (NPM bleibt zentral, siehe Anleitung) vs. Option B (eigenes NGINX). Empfehlung: A zuerst.
docker-compose.yml für den neuen Server anpassen: ports:-Mapping für pawfeed-1/2/3 ergänzen (z. B. 3001:3000, 3002:3000, 3003:3000), damit NPM sie per LAN-IP erreichen kann.
Phase 1 — Paralleler Testbetrieb (kein Cutover, kein Risiko)
docker/.env sicher (scp, nicht Git) auf den neuen Server kopieren, chmod 600
redis_data-Volume vom alten NAS auf den neuen Server klonen (siehe Anleitung — Redis kurz stoppen für konsistenten Export)
./start.sh auf dem neuen Server ausführen — baut Image, startet alle 3 Replicas + Redis
Smoke-Test ohne Domain/DNS-Änderung: direkt gegen http://<neue-server-ip>:3001/api/health prüfen, danach mit curl -H "Host: pawfeed.org" http://<neue-server-ip>:3001/ oder lokalem /etc/hosts-Eintrag auf einem Testrechner gegen die echte App testen
Prüfen: Login (Clerk), Feed laden, Post erstellen, Video-Upload (Mux), Admin-Panel (/p/[ADMIN_SECRET]/) — alles gegen die gleiche Supabase-Prod-DB wie der alte NAS, also mit Vorsicht testen (siehe Risiken unten)
GlitchTip-Events prüfen: kommen Fehler vom neuen Server testweise in glitchtip.pawfeed.org an?
Phase 2 — Reverse-Proxy-Umstellung vorbereiten
Bei Option A:
/data/nginx/custom/http_top.conf auf dem NPM-Container-Volume (altes NAS) anpassen: Upstream-Block von Docker-DNS-Namen (pawfeed-1:3000) auf LAN-IP:Port des neuen Servers (192.168.1.XXX:3001 usw.) umstellen
NPM-Config testen/reloaden (nginx -t im Container, dann reload), noch nicht den alten Stack stoppen — beide Stacks können parallel erreichbar bleiben, solange der Upstream-Block nicht live geschaltet ist
Bei Option B:
NGINX/Traefik/Caddy auf dem neuen Server aufsetzen, Let's-Encrypt-Zertifikat für pawfeed.org per DNS-01 oder HTTP-01-Challenge ausstellen (HTTP-01 erst nach dem eigentlichen Cutover möglich, da Port 80 vom Router noch auf den alten NAS zeigt — ggf. DNS-01 über Cloudflare-API nutzen, um das Henne-Ei-Problem zu vermeiden)
Phase 3 — Cutover (kurzes Wartungsfenster)
Wartungsmodus aktivieren (maintenance.ts / Redis-Flag MAINTENANCE_KEY), falls verfügbar, um Schreibkonflikte während des Fensters zu vermeiden
Alten Stack auf dem NAS stoppen: docker compose stop für pawfeed-1/2/3 (Redis dort ebenfalls stoppen, falls Option A mit geklontem Redis-Volume — sonst laufen zwei Redis-Instanzen mit unterschiedlichem Zustand gegen dieselbe DB)
Bei Option A: NPM-Upstream-Änderung aus Phase 2 live schalten (Reload)
Bei Option B: Router-Portforwarding 80/443 auf neue Server-IP umstellen
Cron-Jobs auf dem neuen Server einrichten (crontab -e): trim-feeds, anniversaries, backup-db.sh — dabei gleich den kaputten localhost:4563-Verweis in run-cron.sh auf https://pawfeed.org/api/cron/... korrigieren
Alte Cron-Einträge auf dem NAS deaktivieren/entfernen (Doppelausführung vermeiden)
Verifikation: curl https://pawfeed.org/api/health, Login-Flow, Post erstellen, Mux-Webhook (/api/webhooks/mux), Stripe-Webhook (/api/webhooks/stripe) — Stripe/Mux/Clerk-Dashboards zeigen weiterhin dieselbe Domain, sollten ohne Neukonfiguration funktionieren
Wartungsmodus deaktivieren
Phase 4 — Nachbereitung
Alten PawFeed-Stack auf dem NAS vollständig abbauen (Container, Volumes, /Dockers/PawFeed-Verzeichnis archivieren statt sofort löschen — für Rollback-Fenster von ein paar Tagen)
CLAUDE.md (Abschnitt „Deployment") und Memory (project_deployment.md) aktualisieren: neue Server-IP, ggf. neuer Deploy-Workflow
Monitoring/Uptime-Kuma (läuft ebenfalls auf dem alten NAS) um einen Check für den neuen Server ergänzen
Rollback-Plan dokumentieren: solange der alte Stack archiviert ist, reicht ein Zurück-Umstellen des NPM-Upstreams (Option A) bzw. der Portweiterleitung (Option B), um binnen Minuten zurückzuwechseln
Risiken / offene Punkte
Doppelbetrieb gegen dieselbe Supabase-Prod-DB: Phase 1 testet den neuen Server gegen echte Prod-Daten (LIVE-Stripe!). Schreibende Aktionen (Testposts, Testzahlungen) während des Parallelbetriebs vermeiden oder klar als Test kennzeichnen — es gibt keine separate Staging-DB.
Redis-Split-Brain: Falls beide Redis-Instanzen (alt + geklont) gleichzeitig aktiv sind, driften Feed-Caches auseinander. Alten Redis-Container vor dem Cutover zuverlässig stoppen.
DNS-Cutover ist eigentlich kein DNS-Cutover: Da pawfeed.org über Cloudflare auf eine DDNS-Adresse zeigt, die auf den Router (nicht auf einen bestimmten internen Server) zeigt, ändert sich bei Option A keine öffentliche Adresse — das reduziert Cutover-Risiko erheblich gegenüber einem klassischen Server-Umzug mit DNS-Propagation.
run-cron.sh-Bug ist schon jetzt aktiv (nicht erst nach dem Umzug) — sollte unabhängig vom Zeitplan dieses Issues zeitnah gefixt werden, damit trim-feeds (Redis-Feed-Trimming, 90-Tage-Fenster) nicht unbegrenzt weiter ausbleibt.
## Migrationsplan (Phasen)
Voraussetzung für alle Phasen: neuer Server hat Linux, Docker, NGINX/Docker Compose installiert (wie in der Frage genannt) und ist im selben LAN wie der alte NAS erreichbar.
### Phase 0 — Vorbereitung (kein Risiko für Prod)
- [ ] SSH-Zugriff auf neuen Server einrichten (Key-Auth, analog zu `daniel@192.168.1.222`)
- [ ] Docker + Compose Version prüfen (Ziel: ≥ Docker 29.x / Compose v5.x, wie auf dem NAS)
- [ ] Gitea-SSH-Key des neuen Servers zu `admin/petfeed` hinzufügen (falls künftig `git pull` statt `scp`-Deploy)
- [ ] Repo klonen: `git clone ssh://git@192.168.1.222:2222/admin/petfeed.git`
- [ ] Entscheidung treffen: **Option A** (NPM bleibt zentral, siehe Anleitung) vs. **Option B** (eigenes NGINX). Empfehlung: A zuerst.
- [ ] `docker-compose.yml` für den neuen Server anpassen: `ports:`-Mapping für `pawfeed-1/2/3` ergänzen (z. B. `3001:3000`, `3002:3000`, `3003:3000`), damit NPM sie per LAN-IP erreichen kann.
### Phase 1 — Paralleler Testbetrieb (kein Cutover, kein Risiko)
- [ ] `docker/.env` sicher (scp, nicht Git) auf den neuen Server kopieren, `chmod 600`
- [ ] `redis_data`-Volume vom alten NAS auf den neuen Server klonen (siehe Anleitung — Redis kurz stoppen für konsistenten Export)
- [ ] `./start.sh` auf dem neuen Server ausführen — baut Image, startet alle 3 Replicas + Redis
- [ ] Smoke-Test **ohne** Domain/DNS-Änderung: direkt gegen `http://<neue-server-ip>:3001/api/health` prüfen, danach mit `curl -H "Host: pawfeed.org" http://<neue-server-ip>:3001/` oder lokalem `/etc/hosts`-Eintrag auf einem Testrechner gegen die echte App testen
- [ ] Prüfen: Login (Clerk), Feed laden, Post erstellen, Video-Upload (Mux), Admin-Panel (`/p/[ADMIN_SECRET]/`) — alles gegen die **gleiche** Supabase-Prod-DB wie der alte NAS, also mit Vorsicht testen (siehe Risiken unten)
- [ ] GlitchTip-Events prüfen: kommen Fehler vom neuen Server testweise in `glitchtip.pawfeed.org` an?
### Phase 2 — Reverse-Proxy-Umstellung vorbereiten
**Bei Option A:**
- [ ] `/data/nginx/custom/http_top.conf` auf dem NPM-Container-Volume (altes NAS) anpassen: Upstream-Block von Docker-DNS-Namen (`pawfeed-1:3000`) auf LAN-IP:Port des neuen Servers (`192.168.1.XXX:3001` usw.) umstellen
- [ ] NPM-Config testen/reloaden (`nginx -t` im Container, dann reload), **noch nicht** den alten Stack stoppen — beide Stacks können parallel erreichbar bleiben, solange der Upstream-Block nicht live geschaltet ist
**Bei Option B:**
- [ ] NGINX/Traefik/Caddy auf dem neuen Server aufsetzen, Let's-Encrypt-Zertifikat für `pawfeed.org` per DNS-01 oder HTTP-01-Challenge ausstellen (HTTP-01 erst nach dem eigentlichen Cutover möglich, da Port 80 vom Router noch auf den alten NAS zeigt — ggf. DNS-01 über Cloudflare-API nutzen, um das Henne-Ei-Problem zu vermeiden)
### Phase 3 — Cutover (kurzes Wartungsfenster)
- [ ] Wartungsmodus aktivieren (`maintenance.ts` / Redis-Flag `MAINTENANCE_KEY`), falls verfügbar, um Schreibkonflikte während des Fensters zu vermeiden
- [ ] Alten Stack auf dem NAS stoppen: `docker compose stop` für `pawfeed-1/2/3` (Redis dort ebenfalls stoppen, falls Option A mit geklontem Redis-Volume — sonst laufen zwei Redis-Instanzen mit unterschiedlichem Zustand gegen dieselbe DB)
- [ ] Bei Option A: NPM-Upstream-Änderung aus Phase 2 live schalten (Reload)
- [ ] Bei Option B: Router-Portforwarding 80/443 auf neue Server-IP umstellen
- [ ] Cron-Jobs auf dem neuen Server einrichten (`crontab -e`): `trim-feeds`, `anniversaries`, `backup-db.sh` — dabei gleich den kaputten `localhost:4563`-Verweis in `run-cron.sh` auf `https://pawfeed.org/api/cron/...` korrigieren
- [ ] Alte Cron-Einträge auf dem NAS deaktivieren/entfernen (Doppelausführung vermeiden)
- [ ] Verifikation: `curl https://pawfeed.org/api/health`, Login-Flow, Post erstellen, Mux-Webhook (`/api/webhooks/mux`), Stripe-Webhook (`/api/webhooks/stripe`) — Stripe/Mux/Clerk-Dashboards zeigen weiterhin dieselbe Domain, sollten ohne Neukonfiguration funktionieren
- [ ] Wartungsmodus deaktivieren
### Phase 4 — Nachbereitung
- [ ] Alten PawFeed-Stack auf dem NAS vollständig abbauen (Container, Volumes, `/Dockers/PawFeed`-Verzeichnis archivieren statt sofort löschen — für Rollback-Fenster von ein paar Tagen)
- [ ] `CLAUDE.md` (Abschnitt „Deployment") und Memory (`project_deployment.md`) aktualisieren: neue Server-IP, ggf. neuer Deploy-Workflow
- [ ] Monitoring/Uptime-Kuma (läuft ebenfalls auf dem alten NAS) um einen Check für den neuen Server ergänzen
- [ ] Rollback-Plan dokumentieren: solange der alte Stack archiviert ist, reicht ein Zurück-Umstellen des NPM-Upstreams (Option A) bzw. der Portweiterleitung (Option B), um binnen Minuten zurückzuwechseln
### Risiken / offene Punkte
- **Doppelbetrieb gegen dieselbe Supabase-Prod-DB:** Phase 1 testet den neuen Server gegen echte Prod-Daten (LIVE-Stripe!). Schreibende Aktionen (Testposts, Testzahlungen) während des Parallelbetriebs vermeiden oder klar als Test kennzeichnen — es gibt keine separate Staging-DB.
- **Redis-Split-Brain:** Falls beide Redis-Instanzen (alt + geklont) gleichzeitig aktiv sind, driften Feed-Caches auseinander. Alten Redis-Container vor dem Cutover zuverlässig stoppen.
- **DNS-Cutover ist eigentlich kein DNS-Cutover:** Da `pawfeed.org` über Cloudflare auf eine DDNS-Adresse zeigt, die auf den Router (nicht auf einen bestimmten internen Server) zeigt, ändert sich bei Option A **keine** öffentliche Adresse — das reduziert Cutover-Risiko erheblich gegenüber einem klassischen Server-Umzug mit DNS-Propagation.
- **`run-cron.sh`-Bug ist schon jetzt aktiv** (nicht erst nach dem Umzug) — sollte unabhängig vom Zeitplan dieses Issues zeitnah gefixt werden, damit `trim-feeds` (Redis-Feed-Trimming, 90-Tage-Fenster) nicht unbegrenzt weiter ausbleibt.
Cron-Fix erledigt (unabhängig vom Umzugsplan oben, aber hier dokumentiert): run-cron.sh zeigte auf localhost:4563, das seit der 3-Replica-Umstellung (#26) nicht mehr existiert — HTTP 000 seit 2026-08-18.
Behoben mit curl --resolve ... https://pawfeed.neodk.ipv64.de$1 statt localhost:4563. Dabei aufgefallen: das NPM-Origin-Zertifikat ist nur für pawfeed.neodk.ipv64.de gültig, nicht für pawfeed.org selbst (subjectAltName-Mismatch) — funktioniert live nur, weil Cloudflare TLS für pawfeed.org an der Edge terminiert und das Origin-Zertifikat nicht strikt prüft. Das Skript zielt deshalb bewusst auf den DDNS-Hostnamen, nicht auf pawfeed.org.
Beide Endpunkte manuell verifiziert: trim-feeds und anniversaries liefern jetzt HTTP 200. Backup der alten Version liegt als run-cron.sh.bak-20260822 auf dem NAS.
Cron-Fix erledigt (unabhängig vom Umzugsplan oben, aber hier dokumentiert): `run-cron.sh` zeigte auf `localhost:4563`, das seit der 3-Replica-Umstellung (#26) nicht mehr existiert — HTTP 000 seit 2026-08-18.
Behoben mit `curl --resolve ... https://pawfeed.neodk.ipv64.de$1` statt `localhost:4563`. Dabei aufgefallen: das NPM-Origin-Zertifikat ist nur für `pawfeed.neodk.ipv64.de` gültig, nicht für `pawfeed.org` selbst (subjectAltName-Mismatch) — funktioniert live nur, weil Cloudflare TLS für `pawfeed.org` an der Edge terminiert und das Origin-Zertifikat nicht strikt prüft. Das Skript zielt deshalb bewusst auf den DDNS-Hostnamen, nicht auf `pawfeed.org`.
Beide Endpunkte manuell verifiziert: `trim-feeds` und `anniversaries` liefern jetzt `HTTP 200`. Backup der alten Version liegt als `run-cron.sh.bak-20260822` auf dem NAS.
admin
added the question label 2026-08-22 13:33:48 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Eventueller Umzug auf eine eigene Hardware
Angedachte Specs:
Dell Optiplex 5090 Micro | Intel Core i5-10500T 2.30 GHz | 16GB |
Prozessor
Prozessor i5-10500T
Prozessorfamilie Intel® Core™ i5
Anzahl Prozessorkerne 6
Prozessor-Taktfrequenz 2,3 GHz
Prozessor-Boost-Frequenz 3,8 GHz
Arbeitsspeicher
Speicherkapazität 16 GB
Interner Speichertyp DDR4-SDRAM
Speichertaktfrequenz 3200 MHz
RAM-Speicher maximal 64 GB
Speicherlayout 1 x 16 GB
Speicherkartensteckplätze 2x DIMM
Anleitung: Worauf beim Umzug auf einen eigenständigen Server zu achten ist
Bestandsaufnahme per SSH auf
192.168.1.222(2026-08-22): 4-Kern-NAS (i5-6500), 15 GB RAM, Disk 86 % voll (32 GB frei), ~45 Container von anderen Projekten (Immich, Paperless, Gitea selbst, GlitchTip, pflege/gon/stllager, …) teilen sich die Kerne mit den 3 PawFeed-Replicas. Das war schon der Auslöser für die Replica-Skalierung (#26) — ein eigener Server für PawFeed löst das grundsätzlicher.Die gute Nachricht zuerst: fast der ganze Zustand liegt schon extern
PawFeed selbst ist praktisch zustandslos. Auf dem NAS liegen nur:
pawfeed-N-Container (Next.js Standalone,node server.js)pawfeed-redis(Feed-Cache, Rate-Limits, Wartungsmodus-Flag, DAU/MAU-Zählung)Alles andere ist bereits Cloud/extern und muss nicht migriert werden, solange die ENV-Werte gleich bleiben:
Das bedeutet: keine DB-Migration, keine Storage-Kopie, keine Downtime-kritische Datenwanderung für den größten Teil des Systems.
Der eine Punkt, der wirklich Denkarbeit braucht: Netzwerk-Kopplung an NPM
Aktuell hängen NPM (Nginx Proxy Manager) und die PawFeed-Container im selben Docker-Bridge-Netzwerk (
nginx_default) auf derselben Maschine — NPM erreicht die Replicas per Container-DNS-Name (pawfeed-1:3000usw., siehedocker/npm-upstream.conf). Sobald PawFeed auf anderer physischer Hardware läuft, existiert dieses gemeinsame Docker-Netzwerk nicht mehr — NPM kann die Container nicht mehr per Docker-DNS erreichen.Zwei Optionen:
Option A (empfohlen): NPM bleibt der zentrale Reverse Proxy auf dem alten NAS.
docker/npm-upstream.conf-Äquivalent (/data/nginx/custom/http_top.confauf dem NPM-Volume) bekommt statt Docker-DNS-Namen die LAN-IP des neuen Servers + gemappte Ports, z. B.:docker-compose.ymlauf dem neuen Server muss die 3 Replica-Ports auf den Host mappen (ports: "3001:3000"etc.) — aktuell macht das compose-File das nicht, weil es sich auf das gemeinsame Docker-Netzwerk verlässt.Option B: Eigenständiges NGINX (oder Caddy/Traefik) direkt auf dem neuen Server.
pawfeed.orgEmpfehlung: mit Option A starten (kleinerer Blast-Radius, reversibel), Option B als Folgeschritt, sobald der neue Server sich bewährt hat.
Checkliste nach Bereich
DNS / Domain
pawfeed.orgzeigt weiterhin auf dieselbe DDNS-Adresse/denselben Router.Secrets /
.envdocker/.env(15 Variablen: Supabase, Clerk, Stripe, Mux, Sentry/GlitchTip, SMTP,ADMIN_SECRET,ADMIN_OWNER_ID,CRON_SECRET,REDIS_PASSWORD) sicher auf den neuen Server übertragen — nicht per Git/FTP, sondernscpüber SSH oder Passwort-Manager. Werte bleiben identisch (App-Ebene, nicht Server-Ebene) — kein Rotieren nötig, außer man will es bewusst.chmod 600 docker/.envauf dem neuen Server.Redis-Daten
src/lib/feed-helpers.ts) sind der einzige Redis-Inhalt mit echtem UX-Wert — es gibt keinen Backfill-Mechanismus, der sie aus Postgres neu aufbaut (Feed wird nur beimfanOutPostbei neuen Posts befüllt). Bei einem Kaltstart auf leerem Redis sehen alle Nutzer beim Cutover kurzzeitig leere Feeds, bis neue Posts reinkommen.redis_dataklonen statt neu zu starten — sauberster Weg:tar-Export), passt gut ins Cutover-Wartungsfenster.Datenbank / Storage / Video / Auth / Zahlungen
DB_POOL_MAX=10× 3 Replicas = 30 Verbindungen, weit unter dem Supabase-Limit (200). Bleibt unverändert, unabhängig vom Server.Webhooks (Mux, Stripe, Clerk)
https://pawfeed.org/api/webhooks/...— domain-basiert, nicht server-basiert. Bei Option A (Domain bleibt gleich) ist hier nichts zu tun. Bei Option B ebenfalls nichts zu tun, solange die Domain gleich bleibt (nur das Zertifikat/der Terminierungspunkt wechselt).Monitoring (GlitchTip)
glitchtip-web,-worker,-postgres,-redis), komplett unabhängig vom PawFeed-Compose-Stack, erreichbar überglitchtip.pawfeed.org.Cron-Jobs
daniels Crontab auf dem alten NAS:trim-feeds(4 Uhr),anniversaries(8 Uhr),backup-db.sh(3 Uhr). Müssen auf dem neuen Server neu angelegt werden (crontab -e, Skripte ausdocker/mitnehmen).run-cron.shruft aktuellhttp://localhost:4563— diesen Host-Port gibt es seit der 3-Replica-Umstellung (#26, 2026-08-16) nicht mehr (kein Port-Mapping mehr im compose-File). Cron-Log zeigt seit 2026-08-18 durchgehendHTTP 000—trim-feedsundanniversarieslaufen seit 4 Tagen ins Leere. Sollte so oder so (auch ohne Umzug) aufhttps://pawfeed.org/api/cron/...umgestellt werden. Beim Server-Umzug gleich mit erledigen.backup-db.sh(täglicherpg_dumpder Supabase-DB, 14 Tage Retention) läuft unabhängig vom App-Server — kann 1:1 auf den neuen Server umziehen, sobald dasDIRECT_URL-Secret dort liegt.Hardware-Dimensionierung
Deploy-Workflow
docker/rolling-deploy.sh,start.sh,docker-compose.ymlunverändert vom Repo übernehmen (ggf.ports:-Mapping ergänzen, siehe Option A oben).ssh://git@192.168.1.222:2222/admin/petfeed.git) für den neuen Server einrichten, falls Deploys künftig pergit pullstattscplaufen sollen.Migrationsplan (Phasen)
Voraussetzung für alle Phasen: neuer Server hat Linux, Docker, NGINX/Docker Compose installiert (wie in der Frage genannt) und ist im selben LAN wie der alte NAS erreichbar.
Phase 0 — Vorbereitung (kein Risiko für Prod)
daniel@192.168.1.222)admin/petfeedhinzufügen (falls künftiggit pullstattscp-Deploy)git clone ssh://git@192.168.1.222:2222/admin/petfeed.gitdocker-compose.ymlfür den neuen Server anpassen:ports:-Mapping fürpawfeed-1/2/3ergänzen (z. B.3001:3000,3002:3000,3003:3000), damit NPM sie per LAN-IP erreichen kann.Phase 1 — Paralleler Testbetrieb (kein Cutover, kein Risiko)
docker/.envsicher (scp, nicht Git) auf den neuen Server kopieren,chmod 600redis_data-Volume vom alten NAS auf den neuen Server klonen (siehe Anleitung — Redis kurz stoppen für konsistenten Export)./start.shauf dem neuen Server ausführen — baut Image, startet alle 3 Replicas + Redishttp://<neue-server-ip>:3001/api/healthprüfen, danach mitcurl -H "Host: pawfeed.org" http://<neue-server-ip>:3001/oder lokalem/etc/hosts-Eintrag auf einem Testrechner gegen die echte App testen/p/[ADMIN_SECRET]/) — alles gegen die gleiche Supabase-Prod-DB wie der alte NAS, also mit Vorsicht testen (siehe Risiken unten)glitchtip.pawfeed.organ?Phase 2 — Reverse-Proxy-Umstellung vorbereiten
Bei Option A:
/data/nginx/custom/http_top.confauf dem NPM-Container-Volume (altes NAS) anpassen: Upstream-Block von Docker-DNS-Namen (pawfeed-1:3000) auf LAN-IP:Port des neuen Servers (192.168.1.XXX:3001usw.) umstellennginx -tim Container, dann reload), noch nicht den alten Stack stoppen — beide Stacks können parallel erreichbar bleiben, solange der Upstream-Block nicht live geschaltet istBei Option B:
pawfeed.orgper DNS-01 oder HTTP-01-Challenge ausstellen (HTTP-01 erst nach dem eigentlichen Cutover möglich, da Port 80 vom Router noch auf den alten NAS zeigt — ggf. DNS-01 über Cloudflare-API nutzen, um das Henne-Ei-Problem zu vermeiden)Phase 3 — Cutover (kurzes Wartungsfenster)
maintenance.ts/ Redis-FlagMAINTENANCE_KEY), falls verfügbar, um Schreibkonflikte während des Fensters zu vermeidendocker compose stopfürpawfeed-1/2/3(Redis dort ebenfalls stoppen, falls Option A mit geklontem Redis-Volume — sonst laufen zwei Redis-Instanzen mit unterschiedlichem Zustand gegen dieselbe DB)crontab -e):trim-feeds,anniversaries,backup-db.sh— dabei gleich den kaputtenlocalhost:4563-Verweis inrun-cron.shaufhttps://pawfeed.org/api/cron/...korrigierencurl https://pawfeed.org/api/health, Login-Flow, Post erstellen, Mux-Webhook (/api/webhooks/mux), Stripe-Webhook (/api/webhooks/stripe) — Stripe/Mux/Clerk-Dashboards zeigen weiterhin dieselbe Domain, sollten ohne Neukonfiguration funktionierenPhase 4 — Nachbereitung
/Dockers/PawFeed-Verzeichnis archivieren statt sofort löschen — für Rollback-Fenster von ein paar Tagen)CLAUDE.md(Abschnitt „Deployment") und Memory (project_deployment.md) aktualisieren: neue Server-IP, ggf. neuer Deploy-WorkflowRisiken / offene Punkte
pawfeed.orgüber Cloudflare auf eine DDNS-Adresse zeigt, die auf den Router (nicht auf einen bestimmten internen Server) zeigt, ändert sich bei Option A keine öffentliche Adresse — das reduziert Cutover-Risiko erheblich gegenüber einem klassischen Server-Umzug mit DNS-Propagation.run-cron.sh-Bug ist schon jetzt aktiv (nicht erst nach dem Umzug) — sollte unabhängig vom Zeitplan dieses Issues zeitnah gefixt werden, damittrim-feeds(Redis-Feed-Trimming, 90-Tage-Fenster) nicht unbegrenzt weiter ausbleibt.Cron-Fix erledigt (unabhängig vom Umzugsplan oben, aber hier dokumentiert):
run-cron.shzeigte auflocalhost:4563, das seit der 3-Replica-Umstellung (#26) nicht mehr existiert — HTTP 000 seit 2026-08-18.Behoben mit
curl --resolve ... https://pawfeed.neodk.ipv64.de$1stattlocalhost:4563. Dabei aufgefallen: das NPM-Origin-Zertifikat ist nur fürpawfeed.neodk.ipv64.degültig, nicht fürpawfeed.orgselbst (subjectAltName-Mismatch) — funktioniert live nur, weil Cloudflare TLS fürpawfeed.organ der Edge terminiert und das Origin-Zertifikat nicht strikt prüft. Das Skript zielt deshalb bewusst auf den DDNS-Hostnamen, nicht aufpawfeed.org.Beide Endpunkte manuell verifiziert:
trim-feedsundanniversariesliefern jetztHTTP 200. Backup der alten Version liegt alsrun-cron.sh.bak-20260822auf dem NAS.