← Blog

Weg von Heroku Postgres, ohne Daten zu verlieren

Railyard

7. April 2026 · 8 Min. Lesezeit

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

Heroku bietet schon seit einiger Zeit keine kostenlose Postgres-Stufe mehr an, und der günstigste verbleibende Plan, Essential-0, begrenzt dich für 5 $ im Monat immer noch auf 1 GB Speicher, 20 Verbindungen und ein Limit von 4.000 Tabellen. Für eine Spielerei-App ist das in Ordnung. Für alles, was tatsächlich gewachsen ist, ist es das nicht, und preislich ist es das auch nicht, sobald man es mit dem Betrieb derselben Datenbank anderswo vergleicht. Was die meisten Leute wirklich aufhält, ist nicht die Entscheidung, sondern die Angst, die Daten beim Rauslösen zu beschädigen: Zeilen zu verlieren, in die falsche Kodierung wiederherzustellen oder umzuschalten, bevor die neue Datenbank wirklich bereit ist. Am Ende dieser Anleitung hast du eine vollständige, verifizierte Kopie deiner Heroku-Postgres-Daten auf einer neuen Datenbank laufen, deine App zeigt darauf, und die alte bleibt als Sicherheitsnetz, bis du dir sicher bist.

Was du brauchst

  • Die Heroku CLI installiert und eingeloggt (heroku login), mit Zugriff auf die App, deren Datenbank du migrierst.
  • Einen Ziel-Postgres-Server. Diese Anleitung verwendet einen DigitalOcean Basic Droplet für 6 $/Monat (1 vCPU, 1 GiB RAM, 25 GB SSD), auf dem Postgres in Docker läuft, was bequem für eine Datenbank reicht, die bisher in einem 1-GB-Heroku-Essential-0-Plan lag. Nimm eine größere Box, wenn deine Daten größer sind.
  • pg_dump und pg_restore auf deinem Rechner, passend zur (oder neuer als die) Postgres-Version deiner Heroku-Datenbank. Die Postgres.app oder das postgresql-client-Paket deines Betriebssystems liefert beides.
  • Ein kurzes Wartungsfenster ist nicht streng notwendig, hilft aber, wenn du eine ruhige Zeit wählst, da du DATABASE_URL mittendrin neu setzt.
  • Etwa 10–15 Minuten, mehr, wenn deine Datenbank groß oder deine Verbindung langsam ist.

Schritt 1: Ein frisches Backup auf Heroku erstellen

Stelle nicht aus irgendeinem automatischen Backup wieder her, das Heroku gerade zufällig herumliegen hat. Erstelle jetzt eins, damit du genau weißt, zu welchem Zeitpunkt du migrierst.

heroku pg:backups:capture --app your-app-name

Das löst ein neues Backup auf Herokus Seite aus, im benutzerdefinierten komprimierten Dump-Format von Postgres. Je nach Größe deiner Datenbank kann das von ein paar Sekunden bis zu mehreren Minuten dauern. Prüfe den Status mit:

heroku pg:backups --app your-app-name

Du solltest eine Zeile für das gerade erstellte Backup sehen, mit dem Status Completed und einer Größe, die ungefähr dem entspricht, was du für deine Datenbank erwarten würdest. Steht dort noch Pending oder Running, warte und prüfe erneut, bevor du weitermachst – ein noch laufendes Backup lässt sich nicht herunterladen.

Schritt 2: Das Backup herunterladen

Sobald die Erstellung abgeschlossen ist, lade es auf deinen Rechner herunter:

heroku pg:backups:download --app your-app-name

Das speichert eine Datei namens latest.dump (oder die konkrete Backup-ID, falls du eine angegeben hast) in deinem aktuellen Verzeichnis. Wenn du die Datei lieber an etwas anderes übergeben möchtest, statt sie selbst herunterzuladen, gibt dir heroku pg:backups:url --app your-app-name eine temporäre öffentliche URL zum selben Dump, gültig für etwa eine Stunde.

Du solltest eine latest.dump-Datei auf der Festplatte sehen, mit einer Größe in derselben Größenordnung wie von heroku pg:backups gemeldet.

Schritt 3: Die Ziel-Postgres-Datenbank einrichten

Wenn du zu einer selbst gehosteten Postgres-Instanz statt zu einem anderen gemanagten Anbieter wechselst, bringt dich eine kleine Docker-Compose-Datei schnell zu einer laufenden Datenbank:

services:
  postgres:
    image: postgres:16.4-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: change-me
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  pgdata:

Starte sie mit:

docker compose up -d

Du solltest den Container laufen sehen (docker compose ps) und dich mit psql -h your-server-ip -U appuser -d appdb verbinden können. Wenn du statt dessen auf eine gemanagte Postgres-Instanz eines anderen Anbieters zielst, stelle einfach sicher, dass du deren Connection-String hast und dass sie Verbindungen von deiner IP akzeptiert, bevor du weitermachst.

Schritt 4: Den Dump in die neue Datenbank wiederherstellen

pg_restore liest dasselbe benutzerdefinierte Format, in dem Herokus Backup bereits vorliegt, es gibt also keinen Konvertierungsschritt:

pg_restore --verbose --clean --no-acl --no-owner \
  -h your-server-ip -U appuser -d appdb latest.dump

--clean löscht alle in Konflikt stehenden Objekte, bevor sie neu angelegt werden, was wichtig ist, wenn du bereits Migrationen gegen eine leere Datenbank laufen lassen hast und diese Tabellen enthält, die sonst mit der Wiederherstellung kollidieren würden. --no-acl und --no-owner überspringen Heroku-spezifische Rollen- und Berechtigungsreferenzen, die auf deinem neuen Server nicht existieren und sonst Fehler werfen würden, die du bedenkenlos ignorieren kannst.

Du solltest einen langen Strom von CREATE TABLE-, CREATE INDEX- und COPY-Zeilen durchlaufen sehen, ohne fatale Fehler am Ende. Ein paar already exists- oder does not exist, skipping-Warnungen sind normal und kein Zeichen dafür, dass etwas schiefgegangen ist.

Schritt 5: Überprüfen, dass die Daten wirklich übereinstimmen

Bevor du DATABASE_URL anfasst, prüfe, ob das Übertragene vollständig ist. Vergleiche Zeilenanzahlen Tabelle für Tabelle zwischen Heroku und der neuen Datenbank:

SELECT schemaname, relname, n_live_tup
FROM pg_stat_user_tables
ORDER BY relname;

Führe das gegen beide Datenbanken aus (verbinde dich mit Heroku über heroku pg:psql --app your-app-name) und vergleiche die Ergebnisse. Für alles, was deine App als finanziell oder rechtlich wichtig behandelt, prüfe eine Handvoll konkreter Zeilen anhand des Primärschlüssels stichprobenartig, statt dich allein auf die Zählung zu verlassen.

Du solltest für jede Tabelle übereinstimmende Zeilenanzahlen sehen, ohne dass eine Tabelle auf einer Seite vorhanden und auf der anderen fehlend ist.

Schritt 6: Deine App auf die neue Datenbank zeigen lassen

Sobald du sicher bist, dass die Daten übereinstimmen, aktualisiere, wo auch immer dein DATABASE_URL liegt – deine .env-Datei, die Config-Vars deines neuen Hosts, deinen Secrets-Manager – auf den neuen Connection-String und deploye neu. Wenn dein neues Ziel sslmode=require oder Ähnliches benötigt, setze das jetzt im Connection-String, statt es erst zur Laufzeit zu entdecken.

Du solltest sehen, dass der nächste Deploy deiner App erfolgreich hochkommt und gegen die neue Datenbank liest und schreibt. Beobachte deine Fehlerlogs für ein paar Minuten nach dem Deploy auf alles, was mit der Verbindung zu tun hat.

Schritt 7: Heroku kurz behalten, dann abschalten

Lösche die Heroku-App nicht und stufe ihre Datenbank nicht herab, sobald die Umstellung gut aussieht. Lass sie ein, zwei Tage unberührt weiterlaufen, als Fallback, auf das du zurückschalten kannst, falls etwas auftaucht, das dein Zeilen-Check übersehen hat. Sobald du sicher bist, skaliere Herokus Dyno und Datenbank herunter und entferne sie dann.

Die Abkürzung: mit Railyard

Alles oben ist der manuelle Weg, der sich zu kennen lohnt, weil es das ist, was unter jedem Tool tatsächlich passiert, das behauptet, das für dich zu erledigen. Railyard hat zwei Wege, das meiste davon zu überspringen.

Wenn du die ganze App umziehst, nicht nur die Datenbank, liest Applications → Import deine App direkt von Heroku über ein API-Token: Config-Vars, die Formation (deine Prozesstypen und deren Anzahl) und jedes Postgres-, Redis-, MySQL- oder Memcached-Add-on kommen automatisch mit, zusammen mit benutzerdefinierten Domains. Die Git-URL fügst du selbst hinzu, da Herokus API dein Repository nicht offenlegt. Der Heroku-Import ist derzeit in der Beta-Phase, das heißt, er wurde noch nicht gegen genug echte Accounts gelaufen, um ihn als vollständig erprobt zu bezeichnen, deshalb zeigt dir Railyard den Plan für deine konkrete App, bevor etwas passiert, und du prüfst ihn, bevor du bestätigst. Die Datenbank selbst wird übertragen, indem ein Dump direkt von deinem Heroku-DATABASE_URL in die neue gemanagte Datenbank gestreamt wird, und die alte Heroku-App bedient weiter Traffic, bis die neue URL antwortet und du DNS selbst umgezogen hast.

Wenn du nur die Datenbank umziehen willst und deine App und Deploy-Pipeline dort lässt, wo sie sind, erledigt der eigenständige Import data-Flow genau diese Datenbank-Hälfte der Aufgabe für sich: Füge deine Heroku-Postgres-Connection-URL ein (dieselbe, die dir heroku config:get DATABASE_URL liefert), und Railyard zieht die Daten direkt daraus. Es sichert zunächst, was aktuell in der Zieldatenbank liegt, sodass deine bestehenden Daten automatisch wiederhergestellt werden, falls der Import fehlschlägt oder du ihn mittendrin abbrichst, statt halb überschrieben zu bleiben. Wenn du dich entscheidest, nach Abschluss des Imports zu deployen, führt es als letzten Schritt die Migrationen deiner App für dich aus.

In jedem Fall lässt sich die Ausgabe von heroku config -s direkt in Railyards Variables → Bulk import einfügen, sodass deine Umgebungsvariablen nicht von Hand neu eingetippt werden müssen.

Vollständige Details zu beiden Flows: Importing from another platform und Databases and backups. Wenn du noch unentschlossen bist, ob du überhaupt umziehen willst, findest du die komplette Anleitung unter Migrating from Heroku, und unser Heroku-Bill-Checker sagt dir ungefähr, was du heute zahlst im Vergleich zu anderswo.

Troubleshooting

heroku: command not found oder Authentifizierungsfehler. Stelle sicher, dass die CLI installiert ist, und führe heroku login erneut aus; eine veraltete oder abgelaufene Sitzung zeigt sich als Berechtigungsfehler bei Befehlen, die sonst korrekt aussehen.

Der Backup-Download schlägt fehl oder liefert eine leere Datei. Das bedeutet fast immer, dass die Erstellung aus Schritt 1 noch nicht wirklich abgeschlossen war. Prüfe erneut heroku pg:backups --app your-app-name und warte auf Completed, bevor du herunterlädst oder eine URL anfragst.

pg_restore-Fehler über Objekte, die bereits existieren. Das passiert, wenn du in eine Datenbank wiederherstellst, die bereits in Konflikt stehende Tabellen hat, meist eine, in der schon Migrationen gelaufen sind. Verwende --clean wie oben gezeigt, oder stelle in eine wirklich leere Datenbank wieder her und führe deine eigenen Migrationen statt des Dump-Schemas aus.

SSL- oder sslmode-Verbindungsfehler. Heroku Postgres verlangt für direkten pg_dump/psql-Zugriff eine SSL-Verbindung; wenn du direkt von Heroku dumpst statt von einer heruntergeladenen Backup-Datei, hänge ?sslmode=require an den Connection-String an. Deine Zieldatenbank hat womöglich das umgekehrte Problem und verweigert SSL auf einer frischen lokalen Installation komplett – lass den Parameter in diesem Fall für diese Seite weg.

Deine App schreibt nach der Umstellung immer noch in die alte Datenbank. Das ist fast immer ein gecachtes DATABASE_URL, ein Secrets-Manager-Wert, der nicht wirklich neu deployt wurde, oder ein Background-Worker, der nicht zusammen mit dem Web-Prozess neu gestartet wurde. Prüfe jeden Prozesstyp separat, nicht nur den, der den HTTP-Traffic bedient.

FAQ

Verursacht das Downtime? Nicht, wenn du die obige Reihenfolge einhältst. Die Heroku-App bedient während der Backup-, Restore- und Verifizierungsschritte weiter Traffic; der einzige Moment, der zählt, ist, wenn du DATABASE_URL umschaltest, typischerweise ein einzelner Redeploy.

Was passiert mit meiner Heroku-App während ich migriere? Nichts, sie läuft genau wie zuvor weiter. Die Backup-Erstellung liest von einem replikaähnlichen Snapshot-Prozess und sperrt deine Produktionstabellen nicht.

Kann ich ohne die Heroku CLI migrieren? Nur teilweise. Du kannst einen Connection-String aus den Config Vars deiner App im Heroku-Dashboard holen und pg_dump direkt dagegen laufen lassen, aber das Erstellen und Herunterladen eines Backups erfordert speziell die CLI (oder Railyard, das direkt von der Live-URL zieht, was gar keine CLI braucht).

Muss ich die Postgres-Versionen exakt abgleichen? Nein, aber dein Ziel sollte dieselbe Hauptversion oder eine neuere sein. Das Wiederherstellen eines neueren Heroku-Postgres-Dumps in ein älteres Ziel kann an Features scheitern, die in der älteren Version noch nicht existierten.

Was, wenn meine Datenbank größer ist, als in einen schnellen Backup-/Restore-Zyklus passt? Der Prozess ist derselbe, er dauert nur länger. Plane speziell für die Schritte 1 und 4 mehr Zeit ein und erwäge, die Wiederherstellung vorübergehend auf einer Box mit mehr RAM laufen zu lassen, wenn pg_restore langsam ist.

Welchen Weg du auch nimmst, manuell oder über Railyard – die Disziplin, die deine Daten wirklich schützt, ist dieselbe: erfassen, bevor du etwas anfasst, verifizieren, bevor du umschaltest, und die alte Kopie nicht löschen, bis du Zeit hattest, sicher zu sein.

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.

Dump und Restore überspringen

Füge deine Heroku-Postgres-Connection-URL in Railyard ein, und es sichert, streamt die Daten und migriert für dich.

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