Saia do Heroku Key-Value Store (Redis)
Siga isso no seu próprio servidor. Comece grátis, sem cartão
Se você provisionou Redis no Heroku em qualquer momento nos últimos anos, o add-on que você está vendo hoje se chama Heroku Key-Value Store, não Heroku Redis. O Heroku renomeou o produto e moveu ele pro Valkey, um fork compatível com Redis, mantendo o mesmo plugin de CLI heroku-redis e os mesmos nomes de config var. Nada na sua aplicação quebrou, mas é um sinal de pra onde o produto está indo, e é um bom momento pra decidir se você prefere rodar o próprio Redis, num servidor que você controla, por uma fração do preço do add-on.
Este guia cobre as duas partes dessa mudança: montar o seu próprio Redis, e de fato trazer os dados da instância do Heroku pra ele sem perder nada do que a sua aplicação depende.
O que você vai precisar
- Um servidor. Um Hetzner CX23 (2 vCPU, 4 GB de RAM, 40 GB NVMe, €5,99/mês sem IVA) tem espaço de sobra pra rodar Redis junto com uma aplicação web pequena a média. Se você já tem um servidor rodando a sua aplicação, você pode adicionar Redis a ele em vez de provisionar um novo.
- Docker e o plugin Docker Compose instalados nesse servidor (ou o pacote
redis-serverda sua distribuição, se você preferir pular containers, coberto no Passo 3). - Acesso SSH ao servidor e um usuário não-root que consiga rodar
dockerousudo. - O Heroku CLI e acesso ao
heroku redisda sua instância atual, pra puxar os dados.
Você não precisa de um domínio pra isso. O Redis fica na rede privada do servidor e a sua aplicação fala com ele diretamente, nunca pela internet pública.
Passo 1: decida pra que você realmente precisa do Redis
Antes de tocar num arquivo de configuração, cheque o que a sua aplicação está armazenando no Redis, porque isso decide o quanto você precisa ter cuidado com a migração e como você configura a persistência depois.
- Cache puro (o
cache_storedo Rails, o framework de cache do Django, um armazenamento de sessão que você pode perder sem problema): a instância nova pode começar vazia. Nada pra migrar, o cache só se repopula conforme as requisições chegam. - Filas de job (Sidekiq, Bull, Celery, RQ): jobs sentados no Redis quando você faz o cutover são trabalho real que ainda não rodou. Você quer ou drenar a fila até zero antes de trocar, ou copiar os dados (Passo 4).
- Qualquer outra coisa que você ficaria chateado de perder (contadores de rate limiter que importam, um leaderboard, um lock com script Lua): copie os dados.
No Heroku, você consegue checar o que de fato está apontado pro Redis com:
heroku config -a your-app-name | grep -i redis
Anote cada nome de variável que você vir. Os add-ons do Heroku historicamente usaram nomes como REDIS_URL, REDISCLOUD_URL, REDISTOGO_URL ou OPENREDIS_URL dependendo de qual provedor de add-on você começou com, e o código da sua aplicação pode estar lendo especificamente um desses em vez de um REDIS_URL genérico.
Passo 2: rode o Redis com Docker Compose
Este guia usa redis:8-alpine, a versão principal estável atual da imagem oficial do Redis, no Alpine pra uma pegada pequena. Se você já está rodando outros serviços com Compose, adicione isso como mais um serviço no mesmo arquivo; senão isso é suficiente por conta própria.
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:
Algumas escolhas aqui valem explicar:
127.0.0.1:6379:6379publica a porta só na interface loopback do servidor. Um"6379:6379"puro publicaria em toda interface, e a publicação de porta do Docker contorna firewalls de host como oufw, então o Redis ficaria acessível pela internet. Se a sua aplicação roda como outro serviço no mesmocompose.yaml, você pode removerportstotalmente e conectar ao hostnameredis.--requirepassdefine uma senha desde o primeiro dia. A imagem oficial vem sem senha configurada. Uma senha é uma linha e te protege se uma porta algum dia acabar exposta por engano.REDISCLI_AUTHé a variável de onde oredis-clilê a senha dele, então os comandos que você roda dentro do container autenticam sem colocar a senha na linha de comando.--appendonly yesativa a persistência AOF, então um restart do container não apaga os seus dados. Se você decidiu no Passo 1 que essa instância é um cache puro, você pode remover isso e o volume totalmente; perder um cache num restart é inofensivo e você economiza I/O de disco.--maxmemorye--maxmemory-policy allkeys-lrulimitam a memória do Redis e dizem pra ele expulsar as chaves usadas há mais tempo quando atingir esse limite, em vez de recusar escritas. Dimensione isso pra uma fração da RAM do seu servidor, não toda ela; deixe espaço pra sua aplicação e pro sistema operacional.
Salve o arquivo como compose.yaml, depois gere uma senha num arquivo .env do lado e suba o Redis:
echo "REDIS_PASSWORD=$(openssl rand -hex 24)" >> .env
docker compose up -d redis
Cheque se o container está de pé:
docker compose ps
Você deve ver o serviço redis com status Up. Depois confirme que ele responde e a senha funciona:
docker compose exec redis redis-cli ping
Você deve ver PONG. Se você ver NOAUTH Authentication required, a variável REDISCLI_AUTH não chegou no container; cheque se o .env está do lado do seu compose.yaml.
Passo 3: a alternativa sem Docker, uma instalação de pacote com systemd
Se você preferir não rodar Docker de forma alguma, o Redis instala de forma limpa como pacote de sistema e vem com a própria unit do systemd.
No Debian ou Ubuntu:
sudo apt-get update
sudo apt-get install -y redis-server
Edite /etc/redis/redis.conf e defina as mesmas escolhas de acima direto no arquivo de configuração:
requirepass your-generated-password-here
appendonly yes
maxmemory 512mb
maxmemory-policy allkeys-lru
bind 127.0.0.1 ::1
A linha bind importa aqui de um jeito que não importava na versão do Compose: sem o isolamento de rede do Docker, o Redis escuta direto no host, então vincule ao loopback (ou à sua interface de rede privada) em vez de 0.0.0.0 a não ser que você precise especificamente de outro servidor alcançando ele.
Reinicie o serviço pra ele pegar as configurações novas, e confirme que ele inicia no boot:
sudo systemctl enable redis-server
sudo systemctl restart redis-server
sudo systemctl status redis-server
Você deve ver active (running) na saída de status, e redis-cli -a your-generated-password-here ping deve responder PONG.
Passo 4: mova os dados
Se o Passo 1 te disse que essa instância só precisa começar vazia, pule pro Passo 5.
Senão, você precisa de um jeito de copiar as chaves. A própria central de ajuda do Heroku diz que não oferece um jeito de baixar dumps RDB, então a cópia de snapshot que você usaria entre dois servidores que você controla não é um caminho que o Heroku suporta. O que de fato funciona contra qualquer servidor Redis ou Valkey é ler cada chave com comandos comuns e escrever na instância nova.
Primeiro, pare o que quer que escreva no Redis (os seus dynos web e workers), pra nada mudar enquanto você copia:
heroku ps:scale web=0 worker=0 -a your-app-name
Isso deixa a aplicação fora do ar pelo tempo da cópia, então escolha um momento tranquilo. Use os nomes dos seus próprios processos; heroku ps -a your-app-name lista eles. Pra uma fila de job, deixe os workers terminarem o que está na fila antes de reduzir eles, se você puder, pra ter menos pra transferir.
Depois salve este script como copy_redis.py. Ele percorre a origem com SCAN, copia strings, hashes, listas, sets e sorted sets, mantém o tempo de vida restante de cada chave, e imprime qualquer chave de outro tipo (streams, por exemplo) pra você lidar 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 é a configuração que o próprio exemplo em Python do Heroku usa, porque o Key-Value Store serve TLS com certificados autoassinados. Rode o script no seu servidor novo, onde o Redis de destino escuta no 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
Se o Heroku CLI não estiver instalado nesse servidor, rode heroku config:get REDIS_URL -a your-app-name na sua própria máquina e cole o valor em SOURCE_URL em vez disso.
Você deve ver uma linha como copied 1520 of 1520 keys no final. Os dois números só diferem pelas chaves que o script listou como puladas ou chaves que expiraram enquanto ele rodava. Cheque a instância nova pelo próprio lado dela também:
docker compose exec redis redis-cli dbsize
Passo 5: aponte a sua aplicação pra instância nova
Atualize a variável que a sua aplicação de fato lê (a que você encontrou no Passo 1) pra connection string nova:
REDIS_URL=redis://:your-generated-password@127.0.0.1:6379/0
Esse é o host certo quando a sua aplicação roda no mesmo servidor fora do Compose. Se a aplicação é outro serviço no mesmo compose.yaml, use redis como host em vez disso (redis://:your-generated-password@redis:6379/0). Note o esquema: redis://, não o rediss:// que o Heroku te deu, porque essa instância não serve TLS.
Inicie os seus processos web e worker no servidor novo, acompanhe os logs pra conexões bem-sucedidas com o Redis, e só então remova o add-on do Heroku. Você pode nomear ele pelo nome de attachment:
heroku addons:destroy REDIS -a your-app-name
O CLI pede pra você digitar o nome da aplicação pra confirmar. Se puder, mantenha o add-on por um dia ou dois depois do cutover como uma rede de segurança (o plano Mini é limitado a US$3 por mês); ele pega qualquer coisa que ainda aponta pra URL antiga.
O caminho mais rápido: faça isso com a Railyard
Tudo acima é o que a Railyard configura quando você adiciona Redis na aba Resources de uma aplicação. O Redis roda no seu próprio servidor com uma senha, só aplicações nesse servidor conseguem alcançá-lo, e cada aplicação recebe um número de banco de dados nele. A aplicação recebe REDIS_URL, mais REDIS_HOST, REDIS_PORT, REDIS_PASSWORD e REDIS_DB se o seu código lê as partes separadamente. Quando uma aplicação precisa do próprio limite de memória e política de expulsão (noeviction pra uma fila do Sidekiq, allkeys-lru pra um cache), Give this app its own Redis no card do Redis move ela pra uma instância dedicada e copia as chaves dela; Database servers cobre os detalhes.
Pra trazer chaves do Heroku, abra Trusted addresses no card do Redis e permita o seu IP, depois rode o script do Passo 4 a partir da sua própria máquina com TARGET_URL apontando pro endereço e senha que o card mostra.
A parte que economiza mais tempo se você está vindo direto do Heroku: a sua aplicação recebe a connection string como REDIS_URL, e se o seu repositório lê um dos nomes de add-on mais antigos em vez disso (no .env.example ou nos seus arquivos de configuração), a Railyard preenche essa variável com a mesma connection string. Isso cobre REDISCLOUD_URL, REDISTOGO_URL, OPENREDIS_URL, REDISGREEN_URL, REDIS_TLS_URL e nomes terminando em _REDIS_URI, então código escrito pra um add-on antigo continua funcionando sem renomear nada.
Se você está movendo a aplicação inteira, não só o Redis, importar direto do Heroku mapeia um add-on heroku-redis ou rediscloud pro Redis automaticamente como parte da importação, junto com as suas config vars e formation de processos. O passo a passo completo pra mover tudo está em Migrating from Heroku.
Resolução de problemas
As escritas começam a falhar quando a instância enche. Cheque o maxmemory-policy. A política padrão do Redis, noeviction, rejeita escritas novas quando a memória enche em vez de expulsar qualquer coisa, o que é correto pra uma fila onde você não pode perder dados, mas surpreendente se você copiou uma configuração de cache esperando que chaves antigas simplesmente expirassem. Defina allkeys-lru ou volatile-lru pra dados só de cache, e mantenha noeviction deliberadamente pra qualquer coisa que você trata como fila ou fonte de verdade.
Os dados desaparecem depois de um restart do container ou reboot do servidor. Isso significa que a persistência AOF ou RDB não está de fato ativada, geralmente porque --appendonly yes ou appendonly yes ficou de fora, ou o diretório de dados não está montado num volume real. Cheque se docker compose config mostra o mount do volume, e confirme que a persistência está ativa com redis-cli CONFIG GET appendonly.
A sua aplicação não consegue conectar de forma alguma. A causa mais comum depois de uma migração do Heroku é uma URL antiga rediss:// (TLS) apontando pra uma instância redis:// simples, ou o contrário. Os add-ons do Heroku usam TLS; uma instância self-hosted geralmente não usa a não ser que você tenha colocado ela atrás de stunnel ou algo parecido. Faça o esquema na URL corresponder ao que o servidor de fato fala.
Falta de memória num servidor pequeno. Se maxmemory não estiver definido, o Redis vai tentar usar tranquilamente toda a RAM disponível, e numa máquina de 4 GB compartilhada com a sua aplicação e o sistema operacional, isso termina com o OOM killer do kernel derrubando qualquer processo que pareça maior, não necessariamente o Redis. Defina maxmemory explicitamente pra algo bem abaixo da sua RAM total.
Perguntas frequentes
Eu preciso de uma senha se o Redis só é acessível a partir do servidor da minha própria aplicação? Não estritamente, se o seu firewall realmente bloqueia a porta 6379 de qualquer outro lugar. Uma senha ainda é praticamente de graça pra configurar e te protege contra uma regra de firewall que você esqueceu de escrever ou um servidor futuro adicionado na mesma rede.
Eu posso usar a mesma instância de Redis pra cache e filas de job? Sim, separado logicamente por número de banco de dados (redis://host:6379/0 pro cache, /1 pras filas) ou por prefixo de chave, mas seja deliberado sobre a política de expulsão de qualquer jeito; allkeys-lru vai tranquilamente expulsar um job na fila se a instância enche.
Qual a diferença entre Redis e Valkey, a coisa que o add-on do Heroku agora roda? O Valkey é um fork do Redis criado depois que o Redis mudou a licença, mantido como open source sob a Linux Foundation. Ele deu fork a partir do Redis 7.2 e fala o mesmo protocolo, então as bibliotecas cliente de Redis usuais conectam nos dois igual. É por isso que o código da sua aplicação não precisou mudar quando o motor do add-on mudou, e por isso que o mesmo código funciona contra o Redis que você roda sozinho.
Como eu faço backup disso? Com o AOF ativo, os arquivos dentro do volume redis-data são os seus dados. Pra uma cópia que você pode mandar pra outro lugar, redis-cli --rdb /data/backup.rdb dentro do container escreve um snapshot num ponto no tempo da sua própria instância; rode a partir do cron e copie o arquivo pra fora do servidor.
Sair de um add-on de Redis gerenciado é principalmente sobre ser honesto consigo mesmo no Passo 1 sobre o que de fato está armazenado nele. Depois de saber isso, o resto é um arquivo Compose, uma senha, e um cutover que você consegue fazer sem ninguém notar.
Por que escolhem o Railyard para isso
- Sua própria conta de cloud. Faça deploy no DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou em qualquer servidor Linux via SSH, cobrado direto pelo seu provedor, sem markup.
- Um preço fixo por equipe. O Free é grátis para sempre: um servidor mais um servidor de banco de dados, três apps, sem cartão. O Pro custa US$19 por mês para até cinco servidores e apps ilimitados. O Team custa US$49 por mês com servidores ilimitados. Sem cobrança por app, por deploy, por assento ou por gigabyte.
- Templates de um clique. Faça deploy de apps open source, como bancos de dados, analytics e ferramentas de dev, no seu próprio servidor em um clique.
- Bancos de dados gerenciados. Postgres, MySQL e Redis com backups programados, verificados por restauração, e recuperação point-in-time.
- Push-to-deploy. Faça deploy a partir do GitHub, GitLab ou da CLI, com HTTPS automático e releases sem downtime.