← Blog

Migrar desde Heroku Key-Value Store (Redis)

Railyard

31 de mayo de 2026 · 11 min de lectura

Sigue esto en tu propio servidor. Empieza gratis, sin tarjeta

Si aprovisionaste Redis en Heroku en algún momento de los últimos años, el addon que ves hoy se llama Heroku Key-Value Store, no Heroku Redis. Heroku renombró el producto y lo trasladó a Valkey, un fork compatible con Redis, mientras mantiene el mismo plugin de CLI heroku-redis y los mismos nombres de variables de configuración. Nada se rompió en tu aplicación, pero es una señal de hacia dónde va el producto, y es un buen momento para decidir si prefieres ejecutar Redis tú mismo, en un servidor que controlas, por una fracción del precio del addon.

Esta guía cubre las dos partes de ese cambio: poner en marcha tu propio Redis, y transferir realmente los datos de la instancia de Heroku hacia él sin perder nada de lo que tu aplicación dependa.

Lo que necesitas

  • Un servidor. Un Hetzner CX23 (2 vCPU, 4 GB de RAM, 40 GB NVMe, 5,99 € al mes sin IVA) tiene de sobra para ejecutar Redis junto a una aplicación web pequeña o mediana. Si ya tienes un servidor que ejecuta tu aplicación, puedes agregarle Redis en lugar de aprovisionar uno nuevo.
  • Docker y el plugin de Docker Compose instalados en ese servidor (o el paquete redis-server de tu distribución, si prefieres evitar contenedores, cubierto en el paso 3).
  • Acceso SSH al servidor y un usuario sin privilegios root que pueda ejecutar docker o sudo.
  • El CLI de Heroku y acceso heroku redis a tu instancia actual, para transferir los datos.

No necesitas un dominio para esto. Redis permanece en la red privada del servidor y tu aplicación le habla directamente, nunca a través de internet pública.

Paso 1: decide para qué usas Redis realmente

Antes de tocar un archivo de configuración, revisa qué está almacenando tu aplicación en Redis, porque eso determina cuánto cuidado necesitas en la migración y cómo configurar la persistencia después.

  • Caché puro (cache_store de Rails, el framework de caché de Django, un almacén de sesiones que puedes darte el lujo de perder): la nueva instancia puede arrancar vacía. No hay nada que migrar, el caché simplemente se vuelve a llenar a medida que llegan solicitudes.
  • Colas de trabajos (Sidekiq, Bull, Celery, RQ): los trabajos que están en Redis al momento del corte son trabajo real que aún no se ejecutó. Conviene vaciar la cola hasta cero antes de cambiar, o copiar los datos (paso 4).
  • Cualquier otra cosa que lamentarías perder (contadores de limitador de tasa que te importan, una tabla de posiciones, un bloqueo con script en Lua): copia los datos.

En Heroku, puedes revisar qué apunta realmente a Redis con:

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

Anota cada nombre de variable que veas. Los addons de Heroku históricamente han usado nombres como REDIS_URL, REDISCLOUD_URL, REDISTOGO_URL u OPENREDIS_URL según el proveedor de addon con el que empezaste, y el código de tu aplicación puede estar leyendo específicamente una de esas en lugar de un REDIS_URL genérico.

Paso 2: ejecuta Redis con Docker Compose

Esta guía usa redis:8-alpine, la versión mayor estable actual de la imagen oficial de Redis, sobre Alpine para una huella pequeña. Si ya ejecutas otros servicios con Compose, agrega esto como otro servicio en el mismo archivo; de lo contrario, esto es suficiente por sí solo.

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:

Vale la pena explicar algunas decisiones aquí:

  • 127.0.0.1:6379:6379 publica el puerto únicamente en la interfaz loopback del servidor. Un simple "6379:6379" lo publicaría en todas las interfaces, y la publicación de puertos de Docker rodea los firewalls del host como ufw, por lo que Redis quedaría alcanzable desde internet. Si tu aplicación corre como otro servicio en el mismo archivo Compose, puedes quitar ports por completo y conectarte al host llamado redis.
  • --requirepass establece una contraseña desde el primer día. La imagen oficial se distribuye sin contraseña configurada. Una contraseña es una sola línea y te protege si algún puerto termina expuesto por error.
  • REDISCLI_AUTH es la variable de la que redis-cli lee su contraseña, así que los comandos que ejecutas dentro del contenedor se autentican sin poner la contraseña en la línea de comandos.
  • --appendonly yes activa la persistencia AOF, de modo que reiniciar el contenedor no borra tus datos. Si en el paso 1 decidiste que esta instancia es un caché puro, puedes quitar esto y el volumen por completo; perder un caché al reiniciar es inofensivo y ahorras E/S de disco.
  • --maxmemory y --maxmemory-policy allkeys-lru limitan la memoria de Redis y le indican que desaloje las claves menos usadas recientemente al llegar a ese límite, en lugar de rechazar escrituras. Dimensiona esto a una fracción de la RAM de tu servidor, no toda ella; deja espacio para tu aplicación y el sistema operativo.

Guarda el archivo como compose.yaml, luego genera una contraseña en un archivo .env junto a él y levanta Redis:

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

Comprueba que el contenedor esté activo:

docker compose ps

Deberías ver el servicio redis con un estado Up. Luego confirma que responde y que la contraseña funciona:

docker compose exec redis redis-cli ping

Deberías ver PONG. Si ves NOAUTH Authentication required, la variable REDISCLI_AUTH no llegó al contenedor; verifica que .env esté junto a tu compose.yaml.

Paso 3: la alternativa sin Docker, una instalación por paquete con systemd

Si prefieres no usar Docker en absoluto, Redis se instala sin problemas como paquete del sistema y trae su propia unidad de systemd.

En Debian o Ubuntu:

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

Edita /etc/redis/redis.conf y define las mismas opciones de arriba directamente en el archivo de configuración:

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

La línea bind importa aquí de una manera que no importaba en la versión con Compose: sin el aislamiento de red de Docker, Redis escucha directamente en el host, así que enlázalo a loopback (o a tu interfaz de red privada) en lugar de 0.0.0.0, a menos que necesites específicamente que otro servidor pueda alcanzarlo.

Reinicia el servicio para que tome los nuevos ajustes, y asegúrate de que arranque al inicio:

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

Deberías ver active (running) en la salida del estado, y redis-cli -a your-generated-password-here ping debería responder PONG.

Paso 4: transfiere los datos

Si el paso 1 te indicó que esta instancia solo necesita arrancar vacía, salta al paso 5.

De lo contrario, necesitas una forma de copiar las claves. El propio centro de ayuda de Heroku dice que no ofrece una manera de descargar volcados RDB, así que la copia por snapshot que usarías entre dos servidores que controlas no es un camino que Heroku admita. Lo que sí funciona contra cualquier servidor Redis o Valkey es leer cada clave con comandos ordinarios y escribirla en la nueva instancia.

Primero, detén todo lo que escribe en Redis (tus dynos web y workers), para que nada cambie mientras copias:

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

Esto deja la aplicación fuera de línea durante el tiempo que dure la copia, así que elige un momento tranquilo. Usa tus propios nombres de proceso; heroku ps -a your-app-name los lista. Para una cola de trabajos, si puedes, deja que los workers terminen lo que está en cola antes de reducirlos, para que haya menos que transferir.

Luego guarda este script como copy_redis.py. Recorre el origen con SCAN, copia strings, hashes, lists, sets y sorted sets, conserva el tiempo de vida restante de cada clave, e imprime cualquier clave de otro tipo (streams, por ejemplo) para que la trates manualmente:

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 es el ajuste que usa el propio ejemplo en Python de Heroku, porque Key-Value Store sirve TLS con certificados autofirmados. Ejecuta el script en tu nuevo servidor, donde el Redis destino escucha en loopback:

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

Si el CLI de Heroku no está instalado en ese servidor, ejecuta heroku config:get REDIS_URL -a your-app-name en tu propia máquina y pega el valor en SOURCE_URL en su lugar.

Deberías ver al final una línea como copied 1520 of 1520 keys. Los dos números solo difieren por las claves que el script listó como omitidas o las claves que expiraron mientras se ejecutaba. Revisa también la nueva instancia desde su lado:

docker compose exec redis redis-cli dbsize

Paso 5: apunta tu aplicación a la nueva instancia

Actualiza la variable que tu aplicación realmente lee (la que encontraste en el paso 1) con la nueva cadena de conexión:

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

Ese es el host correcto cuando tu aplicación corre en el mismo servidor fuera de Compose. Si la aplicación es otro servicio en el mismo compose.yaml, usa redis como host en su lugar (redis://:your-generated-password@redis:6379/0). Fíjate en el esquema: redis://, no el rediss:// que te daba Heroku, porque esta instancia no sirve TLS.

Inicia tus procesos web y worker en el nuevo servidor, vigila los logs para ver conexiones exitosas a Redis, y solo entonces elimina el addon de Heroku. Puedes nombrarlo por su nombre de attachment:

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

El CLI te pide escribir el nombre de la aplicación para confirmar. Si puedes, conserva el addon uno o dos días después del corte como red de seguridad (el plan Mini está topado en 3 $ al mes); así detectas cualquier cosa que aún apunte a la URL anterior.

El atajo: hazlo con Railyard

Todo lo anterior es lo que configura Railyard cuando agregas Redis desde la pestaña Resources de una aplicación. Redis corre en tu propio servidor con una contraseña, solo las aplicaciones de ese servidor pueden alcanzarlo, y a cada aplicación se le asigna un número de base de datos en él. La aplicación recibe REDIS_URL, además de REDIS_HOST, REDIS_PORT, REDIS_PASSWORD y REDIS_DB si tu código lee las partes por separado. Cuando una aplicación necesita su propio límite de memoria y política de desalojo (noeviction para una cola de Sidekiq, allkeys-lru para un caché), Give this app its own Redis en la tarjeta de Redis la mueve a una instancia dedicada y copia sus claves; Database servers cubre los detalles.

Para transferir claves desde Heroku, abre Trusted addresses en la tarjeta de Redis y permite tu IP, luego ejecuta el script del paso 4 desde tu propia máquina con TARGET_URL apuntando a la dirección y la contraseña que muestra la tarjeta.

La parte que más tiempo ahorra si vienes directo de Heroku: tu aplicación recibe la cadena de conexión como REDIS_URL, y si tu repositorio lee en cambio uno de los nombres de addon más antiguos (en .env.example o tus archivos de configuración), Railyard llena esa variable con la misma cadena de conexión. Eso cubre REDISCLOUD_URL, REDISTOGO_URL, OPENREDIS_URL, REDISGREEN_URL, REDIS_TLS_URL y los nombres que terminan en _REDIS_URI, de modo que el código escrito para un addon antiguo sigue funcionando sin renombrarlo.

Si estás moviendo la aplicación completa, no solo Redis, importar directamente desde Heroku asocia automáticamente un addon heroku-redis o rediscloud a Redis como parte de la importación, junto con tus variables de configuración y tu formación de procesos. La guía completa para mover todo está en Migrar desde Heroku.

Solución de problemas

Las escrituras empiezan a fallar cuando la instancia se llena. Revisa maxmemory-policy. La política por defecto de Redis, noeviction, rechaza las nuevas escrituras cuando la memoria está llena en lugar de desalojar algo, lo cual es correcto para una cola de la que no puedes permitirte perder datos, pero sorprendente si copiaste una configuración de caché esperando que las claves viejas simplemente caducaran. Define allkeys-lru o volatile-lru para datos solo de caché, y mantén noeviction deliberadamente para todo lo que trates como cola o fuente de verdad.

Los datos desaparecen tras reiniciar el contenedor o el servidor. Esto significa que la persistencia AOF o RDB en realidad no está habilitada, normalmente porque se omitió --appendonly yes o appendonly yes, o porque el directorio de datos no está montado en un volumen real. Verifica que docker compose config muestre el montaje del volumen, y confirma que la persistencia está activa con redis-cli CONFIG GET appendonly.

Tu aplicación no logra conectarse en absoluto. La causa más común después de una migración desde Heroku es un esquema de URL rediss:// (TLS) que quedó apuntando a una instancia en redis:// simple, o al revés. Los addons de Heroku usan TLS; una instancia autoalojada normalmente no, a menos que la hayas puesto detrás de stunnel o algo similar. Haz que el esquema de la URL coincida con lo que el servidor realmente habla.

Falta de memoria en un servidor pequeño. Si maxmemory no está definido, Redis intentará felizmente usar toda la RAM disponible, y en una máquina de 4 GB compartida con tu aplicación y el sistema operativo, eso termina con el OOM killer del kernel eliminando el proceso que parezca más grande, no necesariamente Redis. Define maxmemory explícitamente a un valor bien por debajo de tu RAM total.

FAQ

¿Necesito una contraseña si Redis solo es alcanzable desde mi propio servidor de aplicación? No estrictamente, si tu firewall realmente bloquea el puerto 6379 desde cualquier otro lugar. Una contraseña sigue siendo casi gratis de configurar y te protege contra una regla de firewall que olvidaste escribir o un futuro servidor agregado a la misma red.

¿Puedo usar la misma instancia de Redis para caché y colas de trabajos? Sí, separadas lógicamente por número de base de datos (redis://host:6379/0 para caché, /1 para colas) o por prefijo de clave, pero sé deliberado con la política de desalojo en cualquier caso; allkeys-lru desalojará sin problema un trabajo en cola si la instancia se llena.

¿Cuál es la diferencia entre Redis y Valkey, lo que ahora ejecuta el addon de Heroku? Valkey es un fork de Redis creado después de que Redis cambiara su licencia, mantenido como open source bajo la Linux Foundation. Se bifurcó de Redis 7.2 y habla el mismo protocolo, así que las bibliotecas cliente de Redis habituales se conectan a cualquiera de los dos. Por eso el código de tu aplicación no tuvo que cambiar cuando cambió el motor del addon, y por eso el mismo código funciona contra el Redis que alojas tú mismo.

¿Cómo hago una copia de respaldo de esto? Con AOF activo, los archivos bajo el volumen redis-data son tus datos. Para una copia que puedas llevar a otro lugar, redis-cli --rdb /data/backup.rdb dentro del contenedor escribe un snapshot en un momento dado de tu propia instancia; ejecútalo desde cron y copia el archivo fuera del servidor.

Dejar de usar un addon de Redis gestionado consiste sobre todo en ser honesto contigo mismo en el paso 1 sobre lo que realmente está almacenado ahí. Una vez que lo sabes, el resto es un archivo Compose, una contraseña, y un corte que puedes hacer sin que nadie se dé cuenta.

Por qué eligen Railyard para esto

  • Tu propia cuenta en la nube. Despliega en DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, o cualquier servidor Linux por SSH, facturado directamente por tu proveedor, sin márgenes adicionales.
  • Un precio fijo por equipo. El plan Free es gratis para siempre: un servidor más un servidor de base de datos, tres apps, sin tarjeta. Pro cuesta $19 al mes por hasta cinco servidores y apps ilimitadas. Team cuesta $49 al mes con servidores ilimitados. Sin cargos por app, por despliegue, por asiento ni por gigabyte.
  • Plantillas de un clic. Despliega apps de código abierto, como bases de datos, analítica y herramientas de desarrollo, en tu propio servidor con un clic.
  • Bases de datos administradas. Postgres, MySQL y Redis, con copias de seguridad programadas y verificadas por restauración, y recuperación a un punto en el tiempo.
  • Despliegue con git push. Despliega desde GitHub, GitLab o la CLI, con HTTPS automático y lanzamientos sin tiempo de inactividad.

Agrega un Redis gestionado en su lugar

Agrega Redis desde la pestaña Resources de una aplicación y Railyard configura un Redis protegido con contraseña en tu servidor y conecta REDIS_URL por ti.

Plan gratis, sin tarjeta, y 14 días de Pro para cada equipo nuevo. Un precio fijo por equipo.