Migrar para a Zenifra

Migrar do Fly.io para a Zenifra

Este guia mostra como traduzir cada parte de um app do Fly.io para o console da Zenifra. No Fly.io, quase tudo está no fly.toml e na CLI fly, então o guia segue esse arquivo seção por seção. Para o roteiro geral, com cópia de dados e troca de DNS, veja Migrar para a Zenifra.

Mantenha o app do Fly.io no ar enquanto você sobe e valida a aplicação na Zenifra. Só destrua as Machines depois de trocar o DNS.

Como os conceitos se traduzem

No Fly.ioNa Zenifra
AppProjeto HTTP
Machines do processo webInstâncias Desejadas ou auto-scaling
Grupos em [processes]Um projeto por grupo
Build com DockerfileImagem OCI ou Repositório Git com runtime
internal_portPorta
[env] e secretsVariáveis de Ambiente
release_commandInício do Comando Start ou um passo manual
Checks HTTPHealthcheck (a partir do plano Premium)
Volumes em [[mounts]]Armazenamento persistente
Machines agendadasJobs agendados
Fly Postgres ou Managed PostgresPostgreSQL gerenciado
Upstash RedisCache gerenciado ou Chave-Valor
TigrisArmazém

Uma diferença importante: no Fly.io, auto_stop_machines pode desligar Machines sem tráfego. Na Zenifra, a aplicação fica ligada. No pagamento Por Hora, você paga pelo tempo em execução; veja Qual plano HTTP escolher.

1. Leia o fly.toml

Abra o fly.toml na raiz do repositório e anote cada seção:

fly.toml
app = "meu-app"
primary_region = "gru"

[build]
  dockerfile = "Dockerfile"

[env]
  NODE_ENV = "production"

[deploy]
  release_command = "npx prisma migrate deploy"

[processes]
  web = "node server.js"
  worker = "node worker.js"

[http_service]
  internal_port = 8080
  processes = ["web"]
  min_machines_running = 1

  [[http_service.checks]]
    path = "/health"
    interval = "15s"

[[mounts]]
  source = "dados"
  destination = "/data"
Seção do fly.tomlNo console da Zenifra
[build] dockerfile, image ou builderEscolha da origem; veja a etapa 2
[env]Variáveis de Ambiente
[deploy] release_commandVeja Release command
[processes]Um projeto por grupo; o comando vai no Comando Start
[http_service] internal_portPorta
[http_service] min_machines_running e fly scale countInstâncias Desejadas
[[http_service.checks]] pathCaminho da verificação, em Editar › Healthcheck da aplicação
[[mounts]] destinationDiretório para Persistir
[[vm]] memory e cpusEscolha do plano
[[statics]]Sirva os arquivos pela própria aplicação
primary_regionNão se aplica: a Zenifra roda no Brasil

Confira também na CLI o que não está no arquivo:

fly scale show -a meu-app      # quantidade e tamanho das Machines
fly secrets list -a meu-app    # nomes dos secrets
fly certs list -a meu-app      # domínios com certificado
fly volumes list -a meu-app    # volumes

2. Escolha a origem do projeto

O Fly.io constrói quase todos os apps a partir de um Dockerfile, muitas vezes gerado pelo fly launch. Na Zenifra você tem dois caminhos:

Opção A: repositório Git com runtime

Se o app é Node.js, Python, Bun, Go ou Java, você pode deixar o Dockerfile de lado e publicar pelo repositório. A Zenifra gera o ambiente de build sozinha e ignora o Dockerfile.

No console, clique em + Criar projeto, escolha Aplicação HTTP e o plano. Em Configurações Avançadas, escolha Repositório Git em Origem do Projeto, o Provedor Git GitHub (ou Forgejo), o repositório e a branch. Depois preencha:

De onde vemNo console da Zenifra
Linguagem e versão do FROM do Dockerfile, como node:22Runtime e Versão
Pasta do app no repositórioDiretório raiz
Comando RUN de build do Dockerfile, como npm run buildComando Build
Grupo web em [processes] ou CMD do DockerfileComando Start
internal_portPorta

Veja os comandos de cada linguagem em Runtimes.

Opção B: imagem Docker

Para manter o mesmo Dockerfile, ou para linguagens como PHP, Ruby, Elixir e .NET, publique como Imagem Docker: construa a imagem, envie para um registry, como o GitHub Container Registry ou o Docker Hub, e informe a imagem no projeto.

docker build --platform linux/amd64 -t ghcr.io/minha-org/meu-app:1.0.0 .
docker push ghcr.io/minha-org/meu-app:1.0.0

A imagem precisa incluir linux/amd64. Em Origem do Projeto, escolha Imagem Docker, informe a imagem e, se o registry for privado, as credenciais. Veja Deploy via imagem OCI e Registry privado. A GitHub Action faz a build e o deploy a cada push.

3. Recupere variáveis e secrets

As variáveis da seção [env] estão no fly.toml: copie direto.

Os secrets não podem ser lidos pela CLI: fly secrets list mostra só o nome e um digest de cada um. Pegue os valores na origem, como o gerenciador de senhas da equipe, o seu CI ou o .env usado no fly secrets import. Se não tiver outra cópia, os secrets estão nas variáveis de ambiente de uma Machine em execução, que você pode consultar pelo fly ssh console com cuidado para não deixar os valores no histórico do terminal.

Ao revisar as variáveis:

  • DATABASE_URL e REDIS_URL apontam para os bancos do Fly.io. Troque pelas URLs dos bancos na Zenifra.
  • Credenciais do Tigris (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ENDPOINT_URL_S3 e BUCKET_NAME) mudam se você migrar os arquivos para o Armazém.
  • Variáveis do Fly.io, como FLY_APP_NAME, FLY_REGION, FLY_MACHINE_ID e PRIMARY_REGION, não existem na Zenifra. Se o código usa alguma, crie uma variável com o valor equivalente.
  • Se o código lê PORT, crie PORT com o mesmo valor do campo Porta.

4. Ajuste o que muda entre as plataformas

Release command

No Fly.io, o release_command roda uma vez por deploy, em uma Machine temporária, antes de atualizar o app. Na Zenifra, coloque o comando no início do Comando Start, como npx prisma migrate deploy && node server.js. Ele passa a rodar sempre que uma instância inicia, então precisa ser idempotente. Para migrations longas ou com várias instâncias, rode a migration manualmente antes de publicar.

Processos

Cada grupo de [processes] vira um projeto na Zenifra, com o mesmo repositório ou a mesma imagem e o comando do grupo no Comando Start. Grupos que não recebem tráfego, como workers, usam Exposição Privado - sem rota pública.

Rede privada

No Fly.io, apps se falam por meu-app.internal ou meu-app.flycast. Na Zenifra, troque esses endereços pela URL do projeto de destino, *.clients.zenifra.com ou o domínio dele, e proteja as rotas internas com autenticação. Bancos e serviços gerenciados da Zenifra já têm as próprias URLs de conexão.

Tarefas agendadas

Machines criadas com fly machine run --schedule (hourly, daily, weekly ou monthly) e processos com cron dentro do container viram Jobs agendados. Na Zenifra, a Expressão cron define o horário exato, em UTC, como 0 6 * * * para todo dia às 06:00 UTC.

Arquivos estáticos

A seção [[statics]] fazia o Fly.io servir uma pasta da imagem direto. Na Zenifra, sirva esses arquivos pela própria aplicação, por exemplo com express.static no Node.js ou WhiteNoise no Django.

5. Migre bancos, volumes e arquivos

Postgres

Crie o PostgreSQL gerenciado na Zenifra antes da aplicação. Para copiar os dados, abra um túnel até o banco do Fly.io e use pg_dump na sua máquina.

Managed Postgres: em um terminal, abra o proxy. Por padrão, ele escuta em localhost:16380:

fly mpg proxy ID_DO_CLUSTER

Fly Postgres (app de Postgres): abra o proxy em uma porta livre:

fly proxy 15432:5432 -a meu-app-db

Em outro terminal, use o usuário, a senha e o banco da DATABASE_URL, trocando o host e a porta pelos do proxy:

pg_dump --format=custom --no-owner --no-acl \
  "postgresql://usuario:senha@localhost:15432/banco" > dump.pgdump

pg_restore --no-owner --no-acl --jobs=4 \
  --dbname="postgresql://usuario:senha@host:porta/banco?sslmode=verify-full&sslrootcert=system" \
  dump.pgdump

Com o Managed Postgres, use a porta 16380 no lugar de 15432. Veja Migrar o banco de dados para validar a cópia.

Redis

Crie um cache gerenciado ou um banco Chave-Valor e troque a REDIS_URL. Dados de cache e de sessões costumam ser recriados pela aplicação. Se o Redis guarda filas, deixe os workers do Fly.io esvaziarem a fila antes da troca.

Volumes

No Fly.io, cada volume pertence a uma Machine e não é replicado. Para copiar os arquivos para a sua máquina:

fly ssh sftp get -R -a meu-app /data ./dados-do-volume

Se o app tem mais de uma Machine com volume, repita em cada uma com -s para escolher a Machine, porque cada volume tem dados diferentes.

Na Zenifra, ative o armazenamento persistente na criação do projeto, com o mesmo caminho em Diretório para Persistir, e envie os arquivos por uma rota de importação temporária protegida por senha. Esse caminho não pode ser alterado depois. Os dados do armazenamento persistente são replicados.

Tigris

O Armazém da Zenifra é compatível com S3, como o Tigris. Baixe os arquivos do bucket com a ferramenta que você já usa e envie para um bucket no Armazém com a AWS CLI ou o SDK S3. Depois troque endpoint, chaves e nome do bucket nas variáveis do projeto. Veja Conectar aplicações.

6. Valide e troque o DNS

  1. Abra a URL *.clients.zenifra.com do projeto e teste login, fluxos principais, e-mails, workers e webhooks.
  2. Confira em Logs de build e nos logs da aplicação se não há erros.
  3. Siga o checklist do dia da troca: reduza o TTL do DNS, faça a cópia final dos dados, adicione o domínio na Zenifra e atualize o DNS.
  4. Depois que o DNS apontar para a Zenifra, remova os certificados do domínio no Fly.io com fly certs remove.
  5. Mantenha o app do Fly.io por alguns dias para rollback. Depois, destrua o app, os volumes e os bancos para parar a cobrança.

Problemas comuns

SintomaCausa provávelCorreção
URL pública não respondePorta diferente da internal_port ou app escutando em localhostUse a mesma porta e escute em 0.0.0.0
Imagem não iniciaImagem construída só para ARMConstrua com --platform linux/amd64
Erro ao conectar em *.internalA rede privada do Fly.io não existe na ZenifraUse a URL do projeto ou do banco na Zenifra
Variável vaziaSecret do Fly.io não foi copiadoRecupere o valor na origem e cadastre no projeto
Tabelas não existemO release_command não roda na ZenifraRode as migrations; veja Release command
Worker paradoGrupo de [processes] sem projetoCrie um projeto privado para cada grupo

Precisa de ajuda?

Escreva para [email protected] com o seu fly.toml ou fale pelo WhatsApp +55 (11) 5199-3013.

Última atualização em

Nessa página