← Blog

Migre da Render pro seu próprio servidor

Railyard

24 de abril de 2026 · 12 min de leitura

Siga isso no seu próprio servidor. Comece grátis, sem cartão

O preço da Render sobe rápido quando um serviço web precisa de mais do que a instância mais pequena, e o Postgres gerenciado dela adiciona a própria linha de custo em cima disso. Algumas equipes batem no comportamento de spin-down do plano gratuito e ainda assim não querem pagar por um plano maior só pra manter um projeto paralelo acordado. Outros só querem um servidor que eles controlam, onde nenhuma plataforma decide quais tamanhos de instância existem ou o que acontece se um cartão cadastrado expirar. Este guia move um serviço web da Render, o worker em segundo plano dele se tiver um, e o banco de dados Postgres gerenciado dela pra um servidor que é seu. No final você vai ter a mesma aplicação rodando a partir do próprio Dockerfile dela num servidor que você provisionou, o banco de dados dela restaurado com as mesmas linhas e o mesmo schema, HTTPS emitido automaticamente, e um job de backup mantendo cópias dos dados pra frente.

O que você vai precisar

  • Um servidor novo. Vamos usar um Hetzner CX23 (2 vCPU, 4 GB de RAM, 40 GB NVMe), listado a €5,99 por mês sem IVA. Isso é espaço suficiente pra rodar o container da aplicação, um processo worker se você tiver um, Postgres, e um proxy reverso Caddy na mesma máquina, que cobre o que um serviço web típico da Render mais o banco de dados dele precisam. Se você preferir comparar provedores primeiro, a Railyard mantém um guia de custos de hospedagem com números atuais entre vários deles.
  • Um nome de domínio que você pode apontar pro servidor novo. Uma mudança de registro A leva alguns minutos pra se propagar, e o HTTPS não vai ser emitido até isso acontecer.
  • Uma chave de API da Render. Crie uma em Account Settings no painel da Render (dashboard.render.com/u/settings), depois “Add API Key”. A Render emite chaves com acesso a tudo na sua conta em vez de uma opção limitada e só de leitura, então guarde ela do jeito que você guardaria qualquer outra credencial e apague quando a migração terminar.
  • O repositório da sua aplicação, com o Dockerfile a partir do qual a Render já estava fazendo o build. Se a Render estava fazendo o build do seu serviço a partir de um runtime sem Docker em vez disso, você vai precisar adicionar um Dockerfile agora, já que nada no servidor novo faz o build da sua aplicação pra você de outra forma.

A Railyard também mantém um guia de migração da Render dedicado com uma comparação funcionalidade por funcionalidade, se você quiser ver o que é diferente antes de começar a mover qualquer coisa.

Passo 1: baixe a sua configuração atual da API da Render

Antes de tocar no servidor novo, tenha um registro preciso do que a Render de fato está rodando, em vez de depender da memória ou de um README antigo. A API da Render permite ler de volta tudo com que o seu serviço está configurado.

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}'

Isso lista todo serviço na sua conta com o id dele. Escolha o que você está movendo e baixe o detalhe completo dele, variáveis de ambiente e domínios customizados:

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'

Você deve ver a URL do repositório do seu serviço, a branch, o comando de start e a porta; a lista completa de variáveis de ambiente, incluindo DATABASE_URL se você estiver usando o Postgres gerenciado da Render; e qualquer nome de domínio customizado conectado ao serviço. Anote tudo isso, você vai precisar de cada parte nas etapas abaixo.

Uma coisa que essa API nunca vai te entregar: o conteúdo de qualquer secret file conectado ao serviço. A Render não tem endpoint pra isso, então abra cada um no painel agora e copie o conteúdo manualmente antes de perder acesso à conta.

Passo 2: provisione o servidor novo

Crie o servidor (um Hetzner CX23 ou parecido, rodando Ubuntu), adicione a sua chave SSH durante a criação, e anote o IP público dele. Depois conecte e atualize os pacotes.

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

Você deve ver o prompt do shell voltar sem nenhuma atualização pendente.

Passo 3: instale o Docker

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

Cheque se funcionou:

docker --version
docker compose version

Os dois comandos devem imprimir um número de versão. O script de instalação do Docker já vem com o Compose como plugin, então não existe etapa separada de instalação pra ele.

Passo 4: traga o seu repositório e Dockerfile pro servidor

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

Se a Render já estava fazendo o build desse serviço a partir de um Dockerfile, você já está pronto. Se estava usando um runtime estilo buildpack (Ruby, Node, e assim por diante), adicione um Dockerfile ao repositório agora, já que esse buildpack não existe fora da Render.

Crie um arquivo de ambiente do lado do repositório com os valores que você baixou no Passo 1, mais uma senha pro banco de dados novo:

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

Qualquer outra variável que a Render listou pra você vai nesse mesmo arquivo, e é referenciada a partir do docker-compose.yml nas próximas etapas.

Passo 5: tire o banco de dados da Render

Abra a página Info do banco de dados no painel da Render e encontre a connection string externa mostrada ali (ela funciona de fora da rede da própria Render, diferente da interna que os serviços na Render usam pra alcançá-lo). Use ela pra tirar um dump:

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

Use um pg_dump local correspondente à versão principal do Postgres que você vai rodar no servidor novo (16, pra corresponder à imagem da próxima etapa). Você deve ver um arquivo render_dump.pgsql aparecer, com tamanho mais ou menos proporcional aos seus dados. Confirme que ele é legível antes de continuar:

pg_restore --list render_dump.pgsql | head

Você deve ver uma lista das suas tabelas, sequências e índices.

Passo 6: inicie o Postgres no servidor novo e restaure

Escreva o arquivo Compose pra stack completa agora, pra o Passo 6 só precisar do serviço de banco de dados rodando:

# /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:

Remova o serviço worker se a Render nunca rodou um pra essa aplicação. Suba só o banco de dados por enquanto:

docker compose up -d db

Copie o dump e restaure:

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

Cheque se chegou corretamente:

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

Você deve ver os mesmos nomes de tabela que você viu no pg_restore --list de volta no Passo 5.

Passo 7: suba o resto da stack

docker compose up -d --build

Cheque:

docker compose ps

Você deve ver app, worker (se presente), db e caddy todos listados como rodando.

Passo 8: aponte o DNS e deixe o Caddy emitir HTTPS

Adicione um registro A pro seu domínio apontando pro IP do servidor. Escreva o Caddyfile:

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

Substitua app.example.com por cada domínio que você listou no Passo 1, um bloco por domínio se você tiver mais de um. Recarregue o Caddy pra pegar o arquivo:

docker compose restart caddy

Você deve ver um certificado válido ser emitido em poucos segundos depois do registro de DNS resolver: curl -I https://app.example.com retorna um HTTP/2 200 sem aviso de certificado.

Passo 9: configure backups

Adicione um cron job diário no servidor que faz o dump do banco de dados e mantém uma janela rotativa de cópias:

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

Rode o comando manualmente uma vez pra confirmar antes de esperar até as 3 da manhã:

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

Você deve ver um arquivo .sql.gz com tamanho diferente de zero.

Passo 10: faça o cutover, com a Render ainda rodando como seu plano B

Mantenha o serviço da Render e o banco de dados dele intocados durante todas as etapas acima. Antes de mover o DNS de verdade, confirme que o servidor novo serve a aplicação corretamente com uma requisição direta:

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

Depois que isso, um login, e um caminho de escrita todos passarem a checagem contra o banco de dados restaurado, mova o DNS do domínio pro servidor novo (se você ainda não tinha feito no Passo 8) e dê tempo pra se propagar totalmente. Só depois de confirmar que o tráfego está chegando no servidor novo você deveria pausar ou apagar o serviço da Render.

Passo 11: mantenha a aplicação atualizada

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

O Compose só refaz o build e reinicia os serviços cuja imagem de fato mudou, então uma mudança de código pequena não derruba o banco de dados nem o Caddy. Pra uma aplicação que não pode tolerar nem alguns segundos de tempo de restart, você precisaria de um segundo container de aplicação e uma troca de balanceador de carga em vez disso, que é mais do que essa configuração de servidor único cobre.

O caminho mais rápido: faça isso com a Railyard

Tudo acima (ler a configuração de um serviço Render através da API dele, baixar uma connection string de banco de dados externa, fazer o dump e restaurar) é exatamente o que o Applications → Import da Railyard faz pra você. Aponte ele pra um token de API da Render e ele lê as suas aplicações, mostra um plano por aplicação antes de você confirmar qualquer coisa, e cria a aplicação no servidor que você escolher. Pra um serviço Render especificamente, ele traz o repositório e a branch, variáveis de ambiente, comando de start e domínios customizados; um worker em segundo plano vem como um processo worker sem URL web, a mesma distinção que você faria manualmente no Passo 6. A API da Render ainda não expõe conteúdo de secret file pra ninguém, incluindo a Railyard, então essa é a única parte que você adiciona você mesmo depois da importação, igual ao Passo 1 acima. A importação da Render está atualmente em beta, então leia a tela de plano antes de confirmar, do mesmo jeito que você checaria os valores no Passo 1.

O lado do banco de dados disso também está disponível por conta própria, fora de uma importação completa de aplicação. O Import data da Railyard aceita tanto uma URL de banco de dados de origem em produção quanto um arquivo de dump enviado, faz backup do banco de dados de destino primeiro sempre pra que uma importação falha ou cancelada restaure automaticamente, e pode rodar as migrations da sua aplicação logo depois se você escolher “deploy after import.” Ele cobre dumps de até 5 GiB por padrão.

Detalhes completos estão em Import from another platform e Databases and backups. O plano Free da Railyard cobre uma mudança desse tamanho sem precisar de cartão: um servidor mais um servidor de banco de dados e três aplicações.

Resolução de problemas

Uma chamada curl contra a API da Render retorna 401 ou 403. As chaves de API da Render não têm escopo, não existe opção só de leitura ou limitada, então uma requisição rejeitada quase sempre significa que a chave foi digitada errada, revogada, ou regenerada depois que você copiou ela, não uma permissão faltando. Gere uma nova em Account Settings e tente de novo.

O worker em segundo plano não inicia, ou começa escutando numa porta que não deveria. Um worker em segundo plano da Render nunca teve uma URL pública pra começar, então não dê ao serviço Compose dele uma linha expose ou um bloco no Caddyfile. Se estiver travando em vez disso, cheque se o comando de start dele é o comando do worker (bundle exec sidekiq, um consumidor de fila, e assim por diante), não o comando do serviço web copiado por engano.

A aplicação sobe mas imediatamente dá erro na porta em que está escutando. O comando de start que você copiou no Passo 1 foi ajustado pro ambiente da Render, onde a própria Render injeta $PORT. O seu container novo não vai definir essa variável a não ser que você faça isso, então ou defina PORT explicitamente no bloco environment do Compose pra corresponder ao que você expose, ou edite o comando de start pra vincular numa porta fixa.

O Caddy nunca emite um certificado. Rode docker compose logs caddy e procure falhas de autorização ou timeouts repetidos. Isso quase sempre significa que o registro A do domínio ainda não está resolvendo pra esse servidor. Compare dig +short app.example.com contra o próprio IP do servidor (curl -4 icanhazip.com rodado no servidor) e espere eles corresponderem antes de esperar o HTTPS funcionar.

A aplicação falha ao subir reclamando de um arquivo faltando. Se o caminho no erro corresponde a algum lugar onde a Render armazenou um secret file pra esse serviço, essa é a única coisa que a API da Render nunca te dá. Abra o arquivo no painel da Render, copie o conteúdo, e ou recrie ele como um arquivo que o seu container monta ou lê, ou converta o conteúdo numa variável de ambiente se a aplicação conseguir ler desse jeito em vez disso.

Perguntas frequentes

Isso causa downtime? Não pro banco de dados: o Postgres das duas plataformas continua ativo e legível até o Passo 10, já que você está lendo da cópia da Render e escrevendo na sua própria. A aplicação em si tem uma janela curta entre a mudança de DNS se propagar e o servidor novo estar totalmente aquecido e servindo os mesmos dados, tipicamente bem abaixo de um minuto se você já confirmou que a requisição direta ao IP do Passo 10 funciona.

E o comportamento de spin-down do plano gratuito da Render? Isso é específico dos serviços web gratuitos da Render, que dormem depois de um período de inatividade e levam alguns segundos pra despertar na próxima requisição. Um container no seu próprio servidor roda continuamente uma vez iniciado, não existe um estado de sleep equivalente pra planejar em torno.

Eu posso manter a Render como um plano B durante a mudança? Sim, é exatamente em torno disso que o Passo 10 é construído: deixe o serviço e o banco de dados da Render rodando intocados até você confirmar que logins, escritas e qualquer job em segundo plano todos funcionam corretamente contra o servidor novo, depois mova o DNS e só pause ou cancele a Render depois disso.

Eu preciso recriar os meus domínios customizados eu mesmo? Você já tem a lista completa do Passo 1. Aponte o DNS de cada domínio pro servidor novo e adicione um bloco correspondente ao Caddyfile pra cada um; o Caddy solicita e renova o HTTPS pra cada domínio do mesmo jeito.

A importação da Railyard traz secret files automaticamente? Não. Essa é a única parte que nem o caminho manual nem a importação conseguem automatizar, já que a API da Render não expõe conteúdo de secret file pra nada fora da Render, incluindo a Railyard. Você adiciona esses manualmente, uma vez, depois que a importação termina, igual você faria no Passo 1.

Sair da Render não significa abandonar Postgres gerenciado ou HTTPS automático. Significa rodar as mesmas peças (Docker Compose, Caddy, Postgres) você mesmo, num servidor cujo preço você fixou antes de se inscrever nele. Seja você passando pelas etapas acima manualmente ou deixando o Applications → Import tratar a cópia do repositório, das variáveis e do banco de dados, você acaba com uma aplicação que você consegue ver, entrar via SSH, e fazer backup sem pedir permissão de um painel primeiro.

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.

Pule as etapas manuais

O Applications → Import da Railyard lê o repositório, as variáveis e o banco de dados da sua aplicação Render direto de um token de API e cria ela no servidor que você escolher.

Plano grátis, sem cartão, e 14 dias de Pro para cada equipe nova. Um preço fixo por equipe.