← Blog

Weg vom Heroku Key-Value Store (Redis)

Railyard

31. Mai 2026 · 10 Min. Lesezeit

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

Wenn du in den letzten Jahren irgendwann Redis auf Heroku eingerichtet hast, heißt das Add-on, das du heute siehst, Heroku Key-Value Store, nicht Heroku Redis. Heroku hat das Produkt umbenannt und auf Valkey umgestellt, einen Redis-kompatiblen Fork, während es dasselbe heroku-redis-CLI-Plugin und dieselben Config-Var-Namen beibehalten hat. An deiner App hat sich nichts kaputt gemacht, aber es ist ein Zeichen dafür, wohin das Produkt steuert, und ein guter Moment, um zu entscheiden, ob du Redis lieber selbst betreibst, auf einem Server, den du kontrollierst, für einen Bruchteil des Add-on-Preises.

Diese Anleitung behandelt beide Teile dieses Umzugs: dein eigenes Redis aufsetzen und die Daten tatsächlich von Herokus Instanz hineinbekommen, ohne etwas zu verlieren, auf das deine App angewiesen ist.

Was du brauchst

  • Einen Server. Ein Hetzner CX23 (2 vCPU, 4 GB RAM, 40 GB NVMe, 5,99 €/Monat exkl. MwSt.) hat reichlich Spielraum, um Redis neben einer kleinen bis mittleren Web-App laufen zu lassen. Wenn du bereits einen Server hast, auf dem deine App läuft, kannst du Redis dort hinzufügen statt einen neuen zu provisionieren.
  • Docker und das Docker-Compose-Plugin auf diesem Server installiert (oder das redis-server-Paket deiner Distribution, wenn du Container lieber überspringst, siehe Schritt 3).
  • SSH-Zugang zum Server und einen Nicht-Root-Benutzer, der docker oder sudo ausführen kann.
  • Die Heroku CLI und heroku redis-Zugriff auf deine aktuelle Instanz, um die Daten umzuziehen.

Eine Domain brauchst du dafür nicht. Redis bleibt im privaten Netzwerk des Servers, und deine App spricht direkt mit ihm, nie über das öffentliche Internet.

Schritt 1: Herausfinden, wofür du Redis tatsächlich brauchst

Bevor du eine Konfigurationsdatei anfasst, prüfe, was deine App in Redis ablegt, denn das entscheidet, wie vorsichtig du bei der Migration sein musst und wie du die Persistenz danach konfigurierst.

  • Reiner Cache (Rails cache_store, Djangos Cache-Framework, ein Sessions-Store, dessen Verlust du dir leisten kannst): Die neue Instanz kann leer starten. Nichts zu migrieren, der Cache füllt sich einfach wieder, sobald Requests eintreffen.
  • Job-Queues (Sidekiq, Bull, Celery, RQ): Jobs, die beim Umschalten noch in Redis liegen, sind echte Arbeit, die noch nicht gelaufen ist. Du willst entweder die Queue vor dem Umschalten auf null leerlaufen lassen oder die Daten mit umziehen (Schritt 4).
  • Alles andere, dessen Verlust dich ärgern würde (Rate-Limiter-Zähler, die dir wichtig sind, ein Leaderboard, ein mit Lua geskriptetes Lock): Daten mit umziehen.

Auf Heroku kannst du prüfen, was tatsächlich auf Redis zeigt, mit:

heroku config -a your-app-name | grep -i redis

Notiere dir jeden Variablennamen, den du siehst. Heroku-Add-ons haben historisch Namen wie REDIS_URL, REDISCLOUD_URL, REDISTOGO_URL oder OPENREDIS_URL verwendet, je nachdem, mit welchem Add-on-Anbieter du angefangen hast, und der Code deiner App liest möglicherweise genau einen davon statt eines generischen REDIS_URL.

Schritt 2: Redis mit Docker Compose betreiben

Diese Anleitung verwendet redis:8-alpine, die aktuelle stabile Hauptversion des offiziellen Redis-Images, auf Alpine für einen kleinen Fußabdruck. Wenn du schon andere Services mit Compose betreibst, füge das als weiteren Service in derselben Datei hinzu; sonst reicht das hier allein.

services:
  redis:
    image: redis:8-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
    environment:
      REDISCLI_AUTH: ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    ports:
      - "127.0.0.1:6379:6379"
    restart: unless-stopped

volumes:
  redis-data:

Ein paar Entscheidungen hier sind erklärungswürdig:

  • 127.0.0.1:6379:6379 veröffentlicht den Port nur auf dem Loopback-Interface des Servers. Ein bloßes "6379:6379" würde ihn auf jedem Interface veröffentlichen, und Dockers Port-Veröffentlichung geht an Host-Firewalls wie ufw vorbei, Redis wäre also aus dem Internet erreichbar. Wenn deine App als weiterer Service in derselben Compose-Datei läuft, kannst du ports komplett weglassen und dich mit dem Hostnamen redis verbinden.
  • --requirepass setzt von Anfang an ein Passwort. Das offizielle Image kommt ohne konfiguriertes Passwort. Ein Passwort ist eine Zeile und schützt dich, falls ein Port mal versehentlich exponiert wird.
  • REDISCLI_AUTH ist die Variable, aus der redis-cli sein Passwort liest, sodass Befehle, die du im Container ausführst, authentifizieren, ohne das Passwort auf der Kommandozeile stehen zu haben.
  • --appendonly yes schaltet AOF-Persistenz ein, damit ein Container-Neustart deine Daten nicht löscht. Wenn du in Schritt 1 entschieden hast, dass diese Instanz ein reiner Cache ist, kannst du das und das Volume komplett weglassen; einen Cache beim Neustart zu verlieren ist harmlos und spart Disk-I/O.
  • --maxmemory und --maxmemory-policy allkeys-lru begrenzen den Speicher von Redis und sagen ihm, die am längsten nicht genutzten Keys zu verwerfen, sobald diese Grenze erreicht ist, statt Writes zu verweigern. Bemisse das auf einen Bruchteil des RAMs deines Servers, nicht auf alles; lass Platz für deine App und das Betriebssystem.

Speichere die Datei als compose.yaml, erzeuge dann ein Passwort in einer .env-Datei daneben und fahre Redis hoch:

echo "REDIS_PASSWORD=$(openssl rand -hex 24)" >> .env
docker compose up -d redis

Prüfe, ob der Container läuft:

docker compose ps

Du solltest den redis-Service mit dem Status Up sehen. Bestätige dann, dass er antwortet und das Passwort funktioniert:

docker compose exec redis redis-cli ping

Du solltest PONG sehen. Siehst du NOAUTH Authentication required, hat die Variable REDISCLI_AUTH den Container nicht erreicht; prüfe, dass .env neben deiner compose.yaml liegt.

Schritt 3: Die Alternative ohne Docker, eine Paketinstallation mit systemd

Wenn du gar kein Docker betreiben möchtest, installiert sich Redis saubet als Systempaket und bringt seine eigene systemd-Unit mit.

Auf Debian oder Ubuntu:

sudo apt-get update
sudo apt-get install -y redis-server

Bearbeite /etc/redis/redis.conf und setze dieselben Entscheidungen wie oben direkt in der Konfigurationsdatei:

requirepass your-generated-password-here
appendonly yes
maxmemory 512mb
maxmemory-policy allkeys-lru
bind 127.0.0.1 ::1

Die bind-Zeile ist hier wichtiger, als sie es in der Compose-Version war: Ohne Dockers Netzwerkisolation lauscht Redis direkt auf dem Host, also binde es an Loopback (oder dein privates Netzwerk-Interface) statt an 0.0.0.0, außer du brauchst explizit, dass ein anderer Server es erreicht.

Starte den Dienst neu, damit er die neuen Einstellungen übernimmt, und stelle sicher, dass er beim Booten startet:

sudo systemctl enable redis-server
sudo systemctl restart redis-server
sudo systemctl status redis-server

Du solltest active (running) in der Statusausgabe sehen, und redis-cli -a your-generated-password-here ping sollte mit PONG antworten.

Schritt 4: Die Daten umziehen

Wenn Schritt 1 dir gesagt hat, dass diese Instanz nur leer starten muss, springe zu Schritt 5.

Andernfalls brauchst du eine Methode, die Keys zu kopieren. Herokus eigenes Help Center sagt, dass es keinen Weg bietet, RDB-Dumps herunterzuladen, der Snapshot-Kopiervorgang, den du zwischen zwei selbst kontrollierten Servern nutzen würdest, ist bei Heroku also kein unterstützter Weg. Was gegen jeden Redis- oder Valkey-Server funktioniert, ist, jeden Key mit gewöhnlichen Befehlen zu lesen und in die neue Instanz zu schreiben.

Stoppe zunächst alles, was in Redis schreibt (deine Web-Dynos und Worker), damit sich beim Kopieren nichts ändert:

heroku ps:scale web=0 worker=0 -a your-app-name

Das nimmt die App für die Dauer des Kopiervorgangs offline, wähle also einen ruhigen Moment. Verwende deine eigenen Prozessnamen; heroku ps -a your-app-name listet sie auf. Lass bei einer Job-Queue die Worker möglichst erst abarbeiten, was bereits in der Queue steht, bevor du sie herunterskalierst, damit weniger übertragen werden muss.

Speichere dann dieses Skript als copy_redis.py. Es durchläuft die Quelle mit SCAN, kopiert Strings, Hashes, Listen, Sets und Sorted Sets, behält die verbleibende TTL jedes Keys und gibt jeden Key eines anderen Typs aus (zum Beispiel Streams), damit du ihn von Hand behandeln kannst:

import os
import redis

source_url = os.environ["SOURCE_URL"]
if source_url.startswith("rediss://"):
    src = redis.from_url(source_url, ssl_cert_reqs=None)
else:
    src = redis.from_url(source_url)
dst = redis.from_url(os.environ["TARGET_URL"])

copied = 0
for key in src.scan_iter(count=500):
    kind = src.type(key).decode()
    ttl = src.pttl(key)
    pipe = dst.pipeline()
    pipe.delete(key)
    if kind == "string":
        pipe.set(key, src.get(key))
    elif kind == "hash":
        pipe.hset(key, mapping=src.hgetall(key))
    elif kind == "list":
        pipe.rpush(key, *src.lrange(key, 0, -1))
    elif kind == "set":
        pipe.sadd(key, *src.smembers(key))
    elif kind == "zset":
        pipe.zadd(key, dict(src.zrange(key, 0, -1, withscores=True)))
    else:
        print(f"skipped {key!r} (type {kind})")
        continue
    if ttl > 0:
        pipe.pexpire(key, ttl)
    pipe.execute()
    copied += 1

print(f"copied {copied} of {src.dbsize()} keys")

ssl_cert_reqs=None ist die Einstellung, die Herokus eigenes Python-Beispiel verwendet, weil Key-Value Store TLS mit selbstsignierten Zertifikaten bedient. Führe das Skript auf deinem neuen Server aus, wo das Ziel-Redis auf Loopback lauscht:

sudo apt-get install -y python3-venv
python3 -m venv venv
venv/bin/pip install redis
SOURCE_URL="$(heroku config:get REDIS_URL -a your-app-name)" \
TARGET_URL="redis://:your-generated-password@127.0.0.1:6379/0" \
venv/bin/python copy_redis.py

Falls die Heroku CLI auf diesem Server nicht installiert ist, führe heroku config:get REDIS_URL -a your-app-name auf deinem eigenen Rechner aus und füge den Wert statt dessen in SOURCE_URL ein.

Du solltest am Ende eine Zeile wie copied 1520 of 1520 keys sehen. Die beiden Zahlen unterscheiden sich nur um Keys, die das Skript als übersprungen gelistet hat, oder Keys, die während des Laufs abgelaufen sind. Prüfe die neue Instanz auch von ihrer Seite:

docker compose exec redis redis-cli dbsize

Schritt 5: Deine App auf die neue Instanz zeigen lassen

Aktualisiere, welche Variable deine App tatsächlich liest (die, die du in Schritt 1 gefunden hast), auf den neuen Connection-String:

REDIS_URL=redis://:your-generated-password@127.0.0.1:6379/0

Das ist der richtige Host, wenn deine App auf demselben Server außerhalb von Compose läuft. Ist die App ein weiterer Service in derselben compose.yaml, verwende statt dessen redis als Host (redis://:your-generated-password@redis:6379/0). Achte auf das Schema: redis://, nicht das rediss://, das Heroku dir gegeben hat, denn diese Instanz bedient kein TLS.

Starte deine Web- und Worker-Prozesse auf dem neuen Server, beobachte die Logs auf erfolgreiche Redis-Verbindungen, und entferne erst dann das Heroku-Add-on. Du kannst es über seinen Attachment-Namen ansprechen:

heroku addons:destroy REDIS -a your-app-name

Die CLI fragt dich, den App-Namen zur Bestätigung einzutippen. Wenn möglich, behalte das Add-on ein, zwei Tage nach der Umstellung als Sicherheitsnetz (der Mini-Plan ist auf 3 $ im Monat begrenzt); es fängt alles auf, was noch auf die alte URL zeigt.

Die Abkürzung: mit Railyard

Alles oben ist das, was Railyard einrichtet, wenn du Redis über den Resources-Tab einer App hinzufügst. Redis läuft auf deinem eigenen Server mit einem Passwort, nur Apps auf diesem Server können es erreichen, und jeder App wird eine Datenbanknummer darauf zugewiesen. Die App erhält REDIS_URL, plus REDIS_HOST, REDIS_PORT, REDIS_PASSWORD und REDIS_DB, falls dein Code die Teile einzeln liest. Wenn eine App ihr eigenes Speicherlimit und ihre eigene Eviction-Policy braucht (noeviction für eine Sidekiq-Queue, allkeys-lru für einen Cache), verschiebt Give this app its own Redis auf der Redis-Karte sie in eine eigene Instanz und kopiert ihre Keys mit um; Database servers behandelt die Details.

Um Keys von Heroku umzuziehen, öffne Trusted addresses auf der Redis-Karte und erlaube deine IP, und führe dann das Skript aus Schritt 4 von deinem eigenen Rechner aus, mit TARGET_URL, das auf die Adresse und das Passwort zeigt, die die Karte anzeigt.

Der Teil, der am meisten Zeit spart, wenn du direkt von Heroku kommst: Deine App bekommt den Connection-String als REDIS_URL, und wenn dein Repository statt dessen einen der älteren Add-on-Namen liest (in .env.example oder deinen Konfigurationsdateien), füllt Railyard diese Variable mit demselben Connection-String. Das deckt REDISCLOUD_URL, REDISTOGO_URL, OPENREDIS_URL, REDISGREEN_URL, REDIS_TLS_URL und Namen, die auf _REDIS_URI enden, ab, sodass Code, der gegen ein altes Add-on geschrieben wurde, ohne Umbenennung weiterläuft.

Wenn du die ganze App umziehst, nicht nur Redis, ordnet der direkte Import von Heroku ein heroku-redis- oder rediscloud-Add-on automatisch Redis zu, als Teil des Imports, zusammen mit deinen Config-Vars und der Prozess-Formation. Die vollständige Anleitung für den Umzug von allem findest du unter Migrating from Heroku.

Troubleshooting

Schreibvorgänge schlagen fehl, sobald die Instanz voll ist. Prüfe maxmemory-policy. Die Standard-Redis-Policy, noeviction, lehnt neue Writes ab, sobald der Speicher voll ist, statt etwas zu verwerfen, was für eine Queue korrekt ist, bei der du keine Daten verlieren darfst, aber überraschend, wenn du eine Cache-Konfiguration kopiert hast, die erwartet, dass alte Keys einfach altern. Setze allkeys-lru oder volatile-lru für reine Cache-Daten, und behalte noeviction bewusst für alles, was du als Queue oder Source of Truth behandelst.

Daten verschwinden nach einem Container-Neustart oder Server-Reboot. Das bedeutet, dass AOF- oder RDB-Persistenz nicht tatsächlich aktiviert ist, meist weil --appendonly yes oder appendonly yes weggelassen wurde oder das Datenverzeichnis nicht auf ein echtes Volume gemountet ist. Prüfe, dass docker compose config das Volume-Mount zeigt, und bestätige, dass Persistenz an ist, mit redis-cli CONFIG GET appendonly.

Deine App kann sich gar nicht verbinden. Die häufigste Ursache nach einer Heroku-Migration ist eine übrig gebliebene rediss://-URL (TLS), die auf eine normale redis://-Instanz zeigt, oder umgekehrt. Herokus Add-ons nutzen TLS; eine selbst gehostete Instanz tut das meist nicht, außer du hast sie hinter stunnel oder Ähnliches gestellt. Passe das Schema in der URL an das an, was der Server tatsächlich spricht.

Speichermangel auf einem kleinen Server. Wenn maxmemory nicht gesetzt ist, versucht Redis fröhlich, den gesamten verfügbaren RAM zu nutzen, und auf einer 4-GB-Box, die sich den Speicher mit deiner App und dem Betriebssystem teilt, endet das damit, dass der OOM-Killer des Kernels den Prozess tötet, der am größten aussieht, nicht notwendigerweise Redis. Setze maxmemory explizit auf etwas deutlich unter deinem Gesamt-RAM.

FAQ

Brauche ich ein Passwort, wenn Redis nur vom eigenen App-Server aus erreichbar ist? Nicht streng, wenn deine Firewall den Port 6379 wirklich von überall sonst blockiert. Ein Passwort einzurichten ist trotzdem fast kostenlos und schützt dich vor einer Firewall-Regel, die du zu schreiben vergessen hast, oder einem künftigen Server, der demselben Netzwerk hinzugefügt wird.

Kann ich dieselbe Redis-Instanz für Caching und Job-Queues verwenden? Ja, logisch getrennt nach Datenbanknummer (redis://host:6379/0 für Cache, /1 für Queues) oder nach Key-Präfix, aber sei in jedem Fall bewusst bei der Eviction-Policy; allkeys-lru wird fröhlich einen geschedulten Job verwerfen, wenn die Instanz voll ist.

Was ist der Unterschied zwischen Redis und Valkey, dem, was Herokus Add-on jetzt betreibt? Valkey ist ein Fork von Redis, der entstand, nachdem Redis seine Lizenz geändert hat, und als Open Source unter der Linux Foundation gepflegt wird. Er wurde von Redis 7.2 abgezweigt und spricht dasselbe Protokoll, sodass die üblichen Redis-Client-Bibliotheken sich mit beiden verbinden. Deshalb musste sich der Code deiner App nicht ändern, als sich die Engine des Add-ons änderte, und deshalb funktioniert derselbe Code gegen das Redis, das du selbst betreibst.

Wie sichere ich das? Mit aktiviertem AOF sind die Dateien im redis-data-Volume deine Daten. Für eine Kopie, die du anderswohin schicken kannst, schreibt redis-cli --rdb /data/backup.rdb im Container einen Snapshot deiner eigenen Instanz zu diesem Zeitpunkt; führe das per Cron aus und kopiere die Datei vom Server weg.

Weg von einem gemanagten Redis-Add-on zu kommen, ist vor allem eine Frage der Ehrlichkeit mit dir selbst in Schritt 1 darüber, was tatsächlich darin gespeichert ist. Sobald das klar ist, ist der Rest eine Compose-Datei, ein Passwort und eine Umstellung, die niemand mitbekommt.

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.

Stattdessen ein gemanagtes Redis anhängen

Füge Redis über den Resources-Tab einer App hinzu, und Railyard richtet ein passwortgeschütztes Redis auf deinem Server ein und verdrahtet REDIS_URL für dich.

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