← Blog

Von Render auf deinen eigenen Server umziehen

Railyard

24. April 2026 · 10 Min. Lesezeit

Folge dem auf deinem eigenen Server. Kostenlos starten, keine Karte nötig

Renders Preise steigen schnell an, sobald ein Webservice mehr als die kleinste Instanz braucht, und das verwaltete Postgres kommt noch als eigener Posten dazu. Manche Teams stoßen an das Spin-down-Verhalten der Free-Stufe und wollen trotzdem nicht für einen größeren Plan zahlen, nur um ein Nebenprojekt wach zu halten. Andere wollen einfach einen Server, den sie selbst kontrollieren, bei dem keine Plattform entscheidet, welche Instanzgrößen es gibt oder was passiert, wenn eine hinterlegte Karte abläuft. Diese Anleitung verschiebt einen Render-Webservice, seinen Background-Worker (falls vorhanden) und die verwaltete Postgres-Datenbank auf einen Server, den du selbst besitzt. Am Ende läuft dieselbe App von ihrem eigenen Dockerfile auf einem Server, den du selbst bereitgestellt hast, die Datenbank ist mit denselben Zeilen und demselben Schema wiederhergestellt, HTTPS wird automatisch ausgestellt, und ein Backup-Job hält künftig Kopien der Daten vor.

Was du brauchst

  • Einen neuen Server. Wir verwenden einen Hetzner CX23 (2 vCPU, 4 GB RAM, 40 GB NVMe) für 5,99 € im Monat exkl. MwSt. Das reicht für den App-Container, einen Worker-Prozess (falls vorhanden), Postgres und einen Caddy-Reverse-Proxy auf derselben Box – also für das, was ein typischer Render-Webservice samt Datenbank braucht. Falls du vorher Anbieter vergleichen willst, führt Railyard eine Hosting-Kosten-Übersicht mit aktuellen Zahlen über mehrere Anbieter.
  • Eine Domain, die du auf den neuen Server zeigen lassen kannst. Eine Änderung des A-Records braucht ein paar Minuten, um sich zu verbreiten, und HTTPS wird erst danach ausgestellt.
  • Einen Render-API-Key. Erstelle einen unter Account Settings im Render-Dashboard (dashboard.render.com/u/settings), dann „Add API Key“. Render stellt Keys mit Zugriff auf den gesamten Account aus statt einer eingeschränkten, nur-lesenden Option – behandle ihn also wie jedes andere Credential und lösche ihn, sobald die Migration abgeschlossen ist.
  • Dein App-Repository mit dem Dockerfile, aus dem Render bereits gebaut hat. Falls Render deinen Service stattdessen aus einer Nicht-Docker-Runtime gebaut hat, musst du jetzt ein Dockerfile hinzufügen, da auf dem neuen Server sonst nichts deine App baut.

Railyard führt außerdem eine eigene Render-Migrationsanleitung mit einem Feature-für-Feature-Vergleich, falls du vorab sehen willst, was sich unterscheidet, bevor du etwas verschiebst.

Schritt 1: Aktuelle Konfiguration aus Renders API ziehen

Bevor du den neuen Server berührst, hol dir einen genauen Stand davon, was Render tatsächlich betreibt, statt dich auf dein Gedächtnis oder ein altes README zu verlassen. Renders API lässt dich alles auslesen, mit dem dein Service konfiguriert ist.

export RENDER_API_KEY="rnd_your_key_here"

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services?limit=100" \
  | jq '.[].service | {id, name, type, repo}'

Das listet jeden Service in deinem Account mit seiner ID. Wähle den, den du verschiebst, und ziehe seine vollständigen Details, Umgebungsvariablen und Custom Domains:

SERVICE_ID="srv-xxxxxxxxxxxxxxxxxxxx"

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services/$SERVICE_ID" \
  | jq '{name, type, repo, branch, startCommand: .serviceDetails.envSpecificDetails.startCommand, ports: .serviceDetails.openPorts}'

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services/$SERVICE_ID/env-vars?limit=100" \
  | jq '.[].envVar'

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services/$SERVICE_ID/custom-domains?limit=100" \
  | jq '.[].customDomain.name'

Du solltest die Repository-URL deines Services, Branch, Start-Command und Port sehen; die vollständige Liste der Umgebungsvariablen, einschließlich DATABASE_URL, falls du Renders verwaltetes Postgres nutzt; und alle an den Service gebundenen Custom-Domain-Namen. Schreib dir alles auf, du brauchst jedes Detail in den folgenden Schritten.

Eine Sache gibt dir diese API nie heraus: die Inhalte angehängter Secret-Dateien. Render hat dafür keinen Endpunkt, also öffne jede davon jetzt im Dashboard und kopiere ihren Inhalt per Hand, bevor du den Zugriff auf den Account verlierst.

Schritt 2: Den neuen Server bereitstellen

Erstelle den Server (einen Hetzner CX23 oder ähnlich, mit Ubuntu), füge deinen SSH-Key bei der Erstellung hinzu und notiere die öffentliche IP. Verbinde dich dann und bring die Pakete auf den neuesten Stand.

ssh root@<server-ip>
apt-get update && apt-get upgrade -y

Du solltest sehen, dass der Shell-Prompt zurückkehrt, ohne ausstehende Updates.

Schritt 3: Docker installieren

curl -fsSL https://get.docker.com | sh

Prüfe, ob es funktioniert hat:

docker --version
docker compose version

Beide Befehle sollten eine Versionsnummer ausgeben. Docker liefert Compose als Plugin aus, dafür gibt es also keinen separaten Installationsschritt.

Schritt 4: Repository und Dockerfile auf den Server bringen

git clone <your-repo-url> /opt/app
cd /opt/app

Wenn Render diesen Service bereits aus einem Dockerfile gebaut hat, bist du fertig. Wenn Render eine Buildpack-artige Runtime (Ruby, Node usw.) genutzt hat, füge dem Repo jetzt ein Dockerfile hinzu, da dieses Buildpack außerhalb von Render nicht existiert.

Erstelle neben dem Repo eine Umgebungsdatei mit den in Schritt 1 gezogenen Werten, plus einem Passwort für die neue Datenbank:

# /opt/app/.env
POSTGRES_PASSWORD=choose-a-strong-password-here

Alle weiteren Variablen, die Render dir aufgelistet hat, kommen in dieselbe Datei und werden in den nächsten Schritten aus docker-compose.yml referenziert.

Schritt 5: Die Datenbank aus Render herausholen

Öffne die Info-Seite der Datenbank im Render-Dashboard und finde dort den externen Connection String (der funktioniert von außerhalb Renders eigenem Netzwerk, im Gegensatz zum internen, den Services auf Render zum Zugriff nutzen). Nutze ihn für einen Dump:

pg_dump --format=custom --no-owner --no-acl \
  "postgresql://user:password@dpg-xxxxxxxx.oregon-postgres.render.com/dbname" \
  -f render_dump.pgsql

Nutze ein lokales pg_dump, das zur Postgres-Hauptversion passt, die du auf dem neuen Server einsetzen wirst (16, passend zum Image im nächsten Schritt). Du solltest eine render_dump.pgsql-Datei sehen, deren Größe grob proportional zu deinen Daten ist. Prüfe, ob sie lesbar ist, bevor du weitermachst:

pg_restore --list render_dump.pgsql | head

Du solltest eine Liste deiner Tabellen, Sequenzen und Indizes sehen.

Schritt 6: Postgres auf dem neuen Server starten und wiederherstellen

Schreibe die Compose-Datei für den gesamten Stack jetzt, damit Schritt 6 nur den laufenden Datenbank-Service braucht:

# /opt/app/docker-compose.yml
services:
  app:
    build: .
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app_production
      RAILS_ENV: production
    depends_on:
      - db
    expose:
      - "3000"

  worker:
    build: .
    command: bundle exec sidekiq
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app_production
      RAILS_ENV: production
    depends_on:
      - db

  db:
    image: postgres:16.4-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: app_production
    volumes:
      - pgdata:/var/lib/postgresql/data

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    depends_on:
      - app

volumes:
  pgdata:
  caddy_data:

Entferne den worker-Service, falls Render für diese App nie einen betrieben hat. Starte vorerst nur die Datenbank:

docker compose up -d db

Kopiere den Dump hinein und stelle ihn wieder her:

docker cp render_dump.pgsql $(docker compose ps -q db):/tmp/render_dump.pgsql
docker compose exec db pg_restore --no-owner --no-acl \
  -U app -d app_production /tmp/render_dump.pgsql

Prüfe, ob es korrekt angekommen ist:

docker compose exec db psql -U app -d app_production -c '\dt'

Du solltest dieselben Tabellennamen sehen, die du in pg_restore --list in Schritt 5 gesehen hast.

Schritt 7: Den Rest des Stacks hochfahren

docker compose up -d --build

Prüfen:

docker compose ps

Du solltest app, worker (falls vorhanden), db und caddy alle als laufend aufgeführt sehen.

Schritt 8: DNS umstellen und Caddy HTTPS ausstellen lassen

Füge einen A-Record für deine Domain hinzu, der auf die IP des Servers zeigt. Schreib das Caddyfile:

# /opt/app/Caddyfile
app.example.com {
    reverse_proxy app:3000
}

Ersetze app.example.com durch jede Domain, die du in Schritt 1 aufgelistet hast, ein Block pro Domain, falls du mehrere hast. Lade Caddy neu, damit die Datei übernommen wird:

docker compose restart caddy

Du solltest sehen, dass innerhalb weniger Sekunden nach Auflösung des DNS-Records ein gültiges Zertifikat ausgestellt wird: curl -I https://app.example.com liefert ein HTTP/2 200 ohne Zertifikatswarnung.

Schritt 9: Backups einrichten

Füge einen täglichen Cron-Job auf dem Server hinzu, der die Datenbank dumpt und ein rollierendes Fenster von Kopien behält:

mkdir -p /opt/backups
crontab -e
0 3 * * * docker compose -f /opt/app/docker-compose.yml exec -T db \
  pg_dump -U app app_production | gzip > /opt/backups/app_$(date +\%F).sql.gz \
  && find /opt/backups -name '*.sql.gz' -mtime +14 -delete

Führe den Befehl einmal per Hand aus, um ihn zu bestätigen, bevor du bis 3 Uhr morgens wartest:

docker compose exec -T db pg_dump -U app app_production | gzip > /opt/backups/app_test.sql.gz
ls -lh /opt/backups

Du solltest eine .sql.gz-Datei mit einer Größe größer null sehen.

Schritt 10: Umschalten, mit Render noch als Fallback

Lass den Render-Service und seine Datenbank während aller obigen Schritte unangetastet laufen. Bevor du DNS wirklich umstellst, bestätige, dass der neue Server die App über eine direkte Anfrage korrekt ausliefert:

curl -H "Host: app.example.com" http://<server-ip>

Sobald das, ein Login und ein Schreibpfad gegen die wiederhergestellte Datenbank alle funktionieren, stelle das DNS der Domain auf den neuen Server um (falls du es nicht schon in Schritt 8 getan hast) und gib ihm Zeit, sich vollständig zu verbreiten. Erst nachdem du bestätigt hast, dass der Traffic den neuen Server erreicht, solltest du den Render-Service pausieren oder löschen.

Schritt 11: Die App aktuell halten

cd /opt/app
git pull
docker compose up -d --build

Compose baut und startet nur die Services neu, deren Image sich tatsächlich geändert hat, sodass eine kleine Code-Änderung nicht die Datenbank oder Caddy mit herunterfährt. Für eine App, die nicht mal ein paar Sekunden Neustartzeit verträgt, bräuchtest du stattdessen einen zweiten App-Container und einen Load-Balancer-Swap, was über dieses Single-Server-Setup hinausgeht.

Die Abkürzung: mit Railyard

Alles oben (die Konfiguration eines Render-Services über seine API auslesen, einen externen Datenbank-Connection-String ziehen, dumpen und wiederherstellen) ist genau das, was Railyards Applications → Import für dich erledigt. Zeig darauf mit einem Render-API-Token, und es liest deine Apps aus, zeigt pro App einen Plan, bevor du irgendetwas bestätigst, und erstellt die App auf dem Server deiner Wahl. Für einen Render-Service konkret bringt es Repository und Branch, Umgebungsvariablen, Start-Command und Custom-Domains mit; ein Background-Worker kommt als Worker-Prozess ohne Web-URL herüber, derselbe Unterschied, den du von Hand in Schritt 6 machen würdest. Renders API gibt niemandem Secret-File-Inhalte heraus, auch nicht Railyard, das ist also der eine Teil, den du selbst nach dem Import hinzufügst, genau wie in Schritt 1 oben. Der Render-Import ist derzeit in der Beta-Phase, also lies den Plan-Screen, bevor du bestätigst, genauso wie du die Werte in Schritt 1 doppelt prüfen würdest.

Die Datenbank-Seite davon ist auch eigenständig verfügbar, außerhalb eines vollständigen App-Imports. Railyards Import data akzeptiert entweder eine Live-Quell-Datenbank-URL oder eine hochgeladene Dump-Datei, sichert die Ziel-Datenbank jedes Mal vorher, sodass ein fehlgeschlagener oder abgebrochener Import automatisch wiederherstellt, und kann direkt danach die Migrationen deiner App ausführen, wenn du „deploy after import“ wählst. Es deckt standardmäßig Dumps bis zu 5 GiB ab.

Vollständige Details findest du unter Import from another platform und Databases and backups. Railyards Free-Plan deckt einen Umzug dieser Größe ohne Kreditkarte ab: ein Server plus ein Datenbankserver und drei Apps.

Troubleshooting

Ein curl-Aufruf gegen die Render-API liefert 401 oder 403. Render-API-Keys sind nicht eingeschränkt, es gibt keine nur-lesende oder begrenzte Option, eine abgelehnte Anfrage bedeutet also fast immer, dass der Key falsch eingegeben, widerrufen oder nach dem Kopieren neu generiert wurde, nicht eine fehlende Berechtigung. Erzeuge einen neuen unter Account Settings und versuch es erneut.

Der Background-Worker startet nicht, oder lauscht auf einem Port, auf dem er nicht sollte. Ein Render-Background-Worker hatte nie eine öffentliche URL, gib seinem Compose-Service also keine expose-Zeile oder einen Block im Caddyfile. Falls er stattdessen abstürzt, prüfe, ob sein Start-Command tatsächlich das des Workers ist (bundle exec sidekiq, ein Queue-Consumer usw.), nicht versehentlich das des Webservices.

Die App startet, aber bricht sofort mit einem Fehler zum lauschenden Port ab. Der Start-Command, den du in Schritt 1 kopiert hast, war auf Renders Umgebung abgestimmt, wo Render $PORT selbst injiziert. Dein neuer Container setzt diese Variable nicht, wenn du es nicht selbst tust – setze also entweder PORT explizit im environment-Block von Compose passend zu deinem expose, oder ändere den Start-Command, um an einen festen Port zu binden.

Caddy stellt nie ein Zertifikat aus. Führe docker compose logs caddy aus und such nach wiederholten Autorisierungsfehlern oder Timeouts. Das bedeutet fast immer, dass der A-Record der Domain noch nicht auf diesen Server aufgelöst wird. Vergleiche dig +short app.example.com mit der eigenen IP des Servers (curl -4 icanhazip.com auf dem Server ausgeführt) und warte, bis beide übereinstimmen, bevor du HTTPS erwartest.

Die App startet nicht und beklagt eine fehlende Datei. Wenn der Pfad im Fehler mit einer Stelle übereinstimmt, an der Render eine Secret-Datei für diesen Service gespeichert hat, ist das die eine Sache, die die Render-API nie herausgibt. Öffne die Datei im Render-Dashboard, kopiere ihren Inhalt, und erstelle sie entweder als Datei neu, die dein Container mountet oder liest, oder wandle ihren Inhalt in eine Umgebungsvariable um, falls die App sie so lesen kann.

FAQ

Verursacht das Downtime? Nicht bei der Datenbank: Beide Plattformen bleiben bis Schritt 10 live und lesbar, da du von Renders Kopie liest und auf deine eigene schreibst. Die App selbst hat ein kurzes Fenster zwischen der Verbreitung der DNS-Änderung und dem vollständig aufgewärmten, dieselben Daten ausliefernden neuen Server, typischerweise deutlich unter einer Minute, wenn du die direkte IP-Anfrage in Schritt 10 schon bestätigt hast.

Was ist mit Renders Spin-down-Verhalten in der Free-Stufe? Das betrifft speziell Renders kostenlose Webservices, die nach einer Phase der Inaktivität einschlafen und beim nächsten Request ein paar Sekunden zum Aufwachen brauchen. Ein Container auf deinem eigenen Server läuft durchgehend, sobald er einmal gestartet ist, es gibt keinen entsprechenden Schlafzustand, um den man herumplanen müsste.

Kann ich Render als Fallback während des Umzugs behalten? Ja, genau darum geht es in Schritt 10: Lass den Render-Service und die Datenbank unangetastet laufen, bis du bestätigt hast, dass Logins, Schreibvorgänge und alle Background-Jobs korrekt gegen den neuen Server funktionieren, stell dann DNS um und pausiere oder kündige Render erst danach.

Muss ich meine Custom-Domains selbst neu anlegen? Du hast die vollständige Liste bereits aus Schritt 1. Zeig das DNS jeder Domain auf den neuen Server und füge für jede einen passenden Block im Caddyfile hinzu; Caddy fordert und erneuert HTTPS für jede Domain auf dieselbe Weise.

Bringt Railyards Import Secret-Dateien automatisch mit? Nein. Das ist der eine Teil, den weder der manuelle Weg noch der Import automatisieren kann, da Renders API Secret-File-Inhalte an nichts außerhalb von Render herausgibt, auch nicht an Railyard. Du fügst diese einmal von Hand nach dem Import hinzu, genau wie in Schritt 1.

Von Render wegzuziehen bedeutet nicht, auf verwaltetes Postgres oder automatisches HTTPS zu verzichten. Es bedeutet, dieselben Bausteine (Docker Compose, Caddy, Postgres) selbst zu betreiben, auf einem Server, dessen Preis du festgelegt hast, bevor du dich dafür angemeldet hast. Ob du die Schritte oben von Hand durchgehst oder Applications → Import das Kopieren von Repository, Variablen und Datenbank übernehmen lässt – am Ende hast du eine App, die du sehen, per SSH einloggen und sichern kannst, ohne vorher ein Dashboard um Erlaubnis zu fragen.

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.

Die manuellen Schritte überspringen

Railyards Applications → Import liest Repository, Variablen und Datenbank deiner Render-App direkt über ein API-Token aus und erstellt sie auf dem Server deiner Wahl.

Kostenloser Plan, keine Kreditkarte, und 14 Tage Pro für jedes neue Team. Ein fester Preis pro Team.