Von Railway auf deinen eigenen Server umziehen
Folge dem auf deinem eigenen Server. Kostenlos starten, keine Karte nötig
Railway ist praktisch, bis die Rechnung größer ist als erwartet, oder bis du einen Server willst, in den du dich per SSH einloggen kannst, statt ein Dashboard zwischen dir und der Maschine zu haben. Davon wegzukommen bedeutet zwei getrennte Aufgaben: den Code und die Konfiguration deiner Anwendung irgendwo anders zum Laufen zu bringen, und die Daten aus Railways gemanagtem Postgres herauszubekommen, ohne etwas zu verlieren oder die App länger herunterzufahren, als eine DNS-Änderung braucht. Keine von beiden ist Railway-spezifisch, sobald du die Form erkennst: Konfiguration ziehen, Datenbank dumpen, dasselbe auf einem Server aufstellen, den du kontrollierst, dann DNS umschalten.
Diese Anleitung macht das zuerst von Hand, mit deinem eigenen VPS und Docker Compose, und behandelt dann den One-Click-Import, der ein Railway-Projekt direkt liest und das meiste davon automatisch erledigt.
Was du brauchst
- Einen VPS. Ein Hetzner CX23 (2 vCPU, 4 GB RAM, aktuell 5,99 €/Monat exkl. MwSt.) oder ein DigitalOcean-2-GB-Droplet (12 $/Monat) reicht für die meisten kleinen Apps plus ihre Postgres-Datenbank.
- Ein Railway-Account mit einem API-Token und CLI-Zugang (
npm install -g @railway/cli, dannrailway login). - Die DNS deiner Domain, bereit zum Umbiegen, sobald der neue Server antwortet.
pg_dumpundpg_restorelokal installiert, oder führe sie innerhalb eines Containers aus (beide liefert das offiziellepostgres-Docker-Image mit).
Schritt 1: Herausfinden, was tatsächlich läuft
Bevor du irgendetwas exportierst, verlinke die CLI mit dem Projekt, damit du seine Services und die exakten Variablen jedes einzelnen sehen kannst:
railway login
railway link
railway variable list
Das gibt jede Variable aus, die Railway für den verlinkten Service gesetzt hat, einschließlich selbst generierter wie DATABASE_URL, RAILWAY_PRIVATE_DOMAIN und allem, was du selbst hinzugefügt hast. Kopiere diese Ausgabe irgendwohin, denn das ist der schnellste Weg, jeden API-Key, jedes Feature-Flag und jedes Secret an einem Ort zu haben, ohne sie aus dem Dashboard neu abzutippen.
Schritt 2: Der Datenbank eine öffentliche Adresse geben
Railways Postgres-Plugin lauscht standardmäßig nur auf Railways privatem Netzwerk; DATABASE_URL zeigt auf einen .railway.internal-Hostnamen, den deine eigene Maschine nicht erreichen kann. Um sie von außerhalb Railway zu dumpen, öffne Settings → Networking des Postgres-Service und schalte Public Networking ein, was einen TCP-Proxy und eine neue Variable DATABASE_PUBLIC_URL neben der privaten erzeugt. Liste die Variablen erneut, um sie zu holen:
railway variable list
Railway rechnet Netzwerk-Egress für Traffic durch diesen Proxy ab, das ist also eine einmalige Kosten für die Migration, nichts, das man danach eingeschaltet lassen sollte, außer man braucht es für etwas anderes.
Schritt 3: Die Datenbank dumpen
Mit DATABASE_PUBLIC_URL in der Hand, dumpe sie von deiner eigenen Maschine (oder vom neuen Server, was vermeidet, die Daten zweimal zu ziehen):
pg_dump "$DATABASE_PUBLIC_URL" --format=custom --file=railway-export.dump
Das benutzerdefinierte Format (-Fc) komprimiert den Dump und lässt pg_restore die Wiederherstellung später parallelisieren, was zählt, sobald die Datenbank mehr als ein paar hundert Megabyte groß ist. Lass Railway dabei weiterlaufen; ein Dump fasst die Live-Datenbank nicht an, es gibt also noch keine Downtime.
Schritt 4: Den neuen Server einrichten
Installiere Docker auf dem VPS, unabhängig vom Anbieter auf dieselbe Weise:
curl -fsSL https://get.docker.com | sh
apt-get install -y docker-compose-plugin
Schreibe eine docker-compose.yml mit Postgres, deiner App und Caddy für HTTPS:
services:
db:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
app:
build: .
restart: unless-stopped
env_file: .env
depends_on:
- db
expose:
- "3000"
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- app
volumes:
postgres_data:
caddy_data:
caddy_config:
Wenn Railway deine App bereits aus einem Dockerfile gebaut hat, verwende es unverändert weiter; der Build-Schritt weiß oder interessiert sich nicht dafür, welche Plattform das entstehende Image ausführt. Wenn Railway es ohne eines gebaut hat (über seine eigene Railpack-/Nixpacks-Erkennung), musst du jetzt eines schreiben. Der genaue Inhalt hängt von deiner Sprache ab, aber das Muster ist dasselbe, das in Deploy from a repository behandelt wird: Abhängigkeiten installieren, bauen und den Start-Befehl ausführen, den deine App bereits in der Entwicklung verwendet.
Schritt 5: Die Datenbank wiederherstellen
Fahre zuerst nur die Datenbank hoch, stelle dann darin wieder her:
docker compose up -d db
docker compose exec -T db pg_restore --no-owner --no-acl -U myapp -d myapp < railway-export.dump
--no-owner und --no-acl überspringen Railway-spezifische Rollen- und Berechtigungsanweisungen, die auf diesem Server nicht existieren, da der Dump aus einer Datenbank stammt, die einer anderen Rolle gehörte als der, die du gerade erstellt hast.
Schritt 6: Die Umgebungsvariablen mit umziehen
Übersetze die Ausgabe von railway variable list aus Schritt 1 in eine .env-Datei für den app-Service. Das meiste kopiert sich unverändert; ein paar Werte müssen statt auf Railway auf das neue Setup zeigen:
DATABASE_URL=postgresql://myapp:changeme@db:5432/myapp
REDIS_URL=redis://redis:6379
# alles andere aus `railway variable list`, das deine App direkt liest
Wenn die App Railways Redis oder ein anderes Add-on verwendet hat, füge es als eigenen Service in docker-compose.yml hinzu, genauso wie db definiert ist, und lass die entsprechende URL auf den Namen dieses Service zeigen statt auf einen Railway-Hostnamen.
Schritt 7: Die Caddyfile schreiben und live gehen
myapp.example.com {
reverse_proxy app:3000
}
Zeige den A-Record der Domain auf den neuen Server, warte, bis er aufgelöst wird, und starte dann alles:
docker compose up -d --build
docker compose logs -f caddy
Du solltest sehen, dass Caddy ein Zertifikat erhält und die App unter https://myapp.example.com antwortet. Vergleiche ein paar Seiten oder API-Antworten mit dem noch laufenden Railway-Deployment, bevor du das als abgeschlossen betrachtest.
Schritt 8: Umschalten und Railway abschalten
Sobald der neue Server ein, zwei Tage lang korrekt echten Traffic bedient hat, zeige alle verbleibenden Referenzen darauf (Webhooks, Mobile-App-API-URLs, alles Hardcodierte) und fahre das Railway-Projekt herunter. Behalte die Dump-Datei ein paar Wochen, bevor du sie löschst.
Die Abkürzung: direkt von Railway importieren
Railyards Applications → Import liest Railway-Projekte über ein API-Token und baut einen Plan, bevor etwas erstellt wird, statt zu raten. So funktioniert der Import tatsächlich: Er liest die Services, Umgebungen und Variablen jedes Projekts über Railways eigene API, und für jeden Service übernimmt er das Git-Repository oder genau das Image, das Railway ausführt, dessen Start-Befehl, falls einer gesetzt ist, seine benutzerdefinierten Domains und seine Variablen, bereinigt um Railway-spezifische. Er erkennt auch, welche Variablen auf ein Postgres-, Redis-, MongoDB- oder MySQL-Add-on anhand der Form ihrer URLs zeigen, und bietet an, die passende Datenbank auf dem neuen Server zu provisionieren und die Daten automatisch hineinzukopieren, derselbe pg_dump-in-eine-frische-Datenbank-Ansatz wie oben, ohne dass du ein Terminal anfasst.
Eine Datenbank ohne öffentliche Adresse braucht trotzdem dasselbe Public Networking-Umschalten zuerst in Railway eingeschaltet. Nichts kann den Inhalt einer Datenbank streamen, ohne dass der TCP-Proxy ihn irgendwo erreichbar veröffentlicht.
Starte den Import über die Migrationsseite, füge ein Railway-API-Token ein, prüfe den Plan, den Railyard für jeden Service zeigt, und wähle den Server, auf dem er deployt werden soll. Siehe Import from another platform für das, was automatisch übernommen wird gegenüber dem, was noch von Hand eingetippt werden muss (Railways API legt zum Beispiel keine Inhalte von Secret-Dateien offen), und Hosting costs, falls der ganze Grund für diesen Umzug ist, zu vergleichen, was du tatsächlich zahlst, gegen einen Server, den du selbst provisionierst.
Troubleshooting
pg_dump kann sich nicht verbinden oder hängt. Bestätige, dass Public Networking für den Postgres-Service tatsächlich eingeschaltet ist und dass du DATABASE_PUBLIC_URL kopiert hast, nicht DATABASE_URL; die private löst sich zu einem .railway.internal-Hostnamen auf, den deine Maschine überhaupt nicht erreichen kann, was sich meist als DNS-Auflösungsfehler statt als abgelehnte Verbindung zeigt.
pg_restore-Fehler über Rollen oder Berechtigungen, die nicht existieren. Füge dem Restore-Befehl --no-owner --no-acl hinzu, was Besitz- und Grant-Anweisungen fallen lässt, die an Railways interne Rollennamen gebunden sind.
Die App kann die Datenbank nach der Umstellung nicht erreichen. Prüfe, dass DATABASE_URL in der neuen .env auf den Compose-Servicenamen (db) zeigt, nicht auf localhost oder Railways alten Hostnamen; innerhalb von Docker Compose erreichen sich Services über den Servicenamen im gemeinsamen Netzwerk, nicht über 127.0.0.1.
HTTPS wird auf dem neuen Server nie ausgestellt. Die DNS der Domain zeigt fast sicher noch auf Railway oder hat noch nicht propagiert. Prüfe mit dig myapp.example.com und vergleiche mit der IP des neuen Servers, bevor du Caddy selbst für die Ursache hältst.
Speichermangel beim Wiederherstellen eines großen Dumps auf einem kleinen VPS. pg_restore kann mit den Standardeinstellungen bei einem Dump mit vielen Indizes überraschend viel Speicher verbrauchen. Stelle ohne --jobs wieder her (seriell, langsamer, weniger Speicher) auf allem unter 4 GB RAM, oder skaliere den Server vorübergehend für die Wiederherstellung hoch und danach wieder herunter.
FAQ
Funktioniert das, wenn ich kein Postgres verwende? Dasselbe Muster gilt für MySQL (mysqldump/mysql) und Redis (redis-cli --rdb), wobei sich die genauen Befehle unterscheiden. Railyards Import erkennt auch Redis-, MongoDB- und MySQL-Add-ons auf dieselbe Weise wie Postgres.
Kann ich schrittweise migrieren, einen Service nach dem anderen? Ja. Nichts verlangt, ein ganzes Railway-Projekt auf einmal umzuziehen; importiere oder baue einen Service neu, verifiziere ihn, zieh dann zum nächsten weiter.
Was passiert mit Railways flüchtigem Storage oder Volumes? Alles, was auf ein Railway-Volume geschrieben wurde, muss separat kopiert werden, meist mit scp oder rsync gegen eine Einmal-Shell-Sitzung auf Railway-Seite, bevor du es abschaltest, da weder der Variablenexport der CLI noch ein Datenbank-Dump Dateisystem-Volumes anfasst.
Lohnt sich der Preisunterschied die Migration wirklich? Das hängt vollständig von der Größe und dem Traffic der App ab. Hosting costs und der Heroku-Bill-Checker sind dafür gebaut, diesen Vergleich konkret zu machen statt zu einer Schätzung, auch wenn Letzterer speziell nach Heroku benannt ist.
Die Teile, die das riskant wirken lassen, vor allem die Datenbank, sind auch die mechanischsten, sobald man es einmal gemacht hat: dumpen, wiederherstellen, verifizieren, DNS umschalten. Alles andere ist nur derselbe Container, der irgendwo läuft, das nicht Railway ist.
Warum Leute sich dafür Railyard entscheiden
- Dein eigenes Cloud-Konto. Deploye auf DigitalOcean, Hetzner, Vultr, AWS, OVHcloud oder jedem Linux-Server per SSH, direkt von deinem Anbieter abgerechnet, ohne Aufschlag.
- Ein fester Preis pro Team. Free ist für immer kostenlos: ein Server plus ein Datenbankserver, drei Apps, keine Karte. Pro kostet 19 $ im Monat für bis zu fünf Server und unbegrenzte Apps. Team kostet 49 $ im Monat mit unbegrenzten Servern. Keine Kosten pro App, pro Deployment, pro Sitz oder pro Gigabyte.
- Templates mit einem Klick. Deploye Open-Source-Apps, wie Datenbanken, Analytics und Dev-Tools, mit einem Klick auf deinem eigenen Server.
- Verwaltete Datenbanken. Postgres, MySQL und Redis mit geplanten, wiederherstellungsgeprüften Backups und Point-in-Time-Recovery.
- Push-to-Deploy. Deploye von GitHub, GitLab oder der CLI, mit automatischem HTTPS und Releases ohne Ausfallzeit.