Pular para o conteúdo

Backups

Backup grátis do Supabase com uma GitHub Action, e a pegadinha

Um backup do Supabase com GitHub Action é grátis e serve para muitos apps. O workflow, a connection string certa e o egress de cada execução.

Vlad Tkachenko17 min de leitura
Um banco de dados num invólucro do Supabase, um cano que passa por um medidor com o ponteiro na faixa âmbar e uma fila de arquivos noturnos.

Em resumo

  • Uma GitHub Action de backup do Supabase roda a CLI do Supabase num horário fixo e faz commit do dump num repositório privado. Não custa nada, e para muitos apps é a resposta certa.
  • Cada execução baixa seu banco de dados inteiro, e o Supabase conta isso como egress. O plano gratuito inclui 5 GB por mês para a organização inteira, e um dump diário de um banco perto do limite de 500 MB gasta cerca de três vezes isso.
  • O exemplo do próprio Supabase se conecta com uma string que os runners do GitHub não alcançam. Coloque a string do Session pooler no secret e restaure uma cópia antes de confiar nas outras.

Seu projeto no Supabase está no plano gratuito, então nada está sendo copiado para lugar nenhum, ou está no Pro e toda cópia que ele faz mora dentro da conta que você tem medo de perder. Num tópico justamente sobre isso, alguém disse que a resposta é uma GitHub Action de backup do Supabase: um arquivo pequeno num repositório que exporta seu banco toda noite e não custa nada.

O conselho estava certo. O próprio Supabase publica o workflow, e para muitos apps é o que vale montar esta semana. Esta é a parte que esses tópicos deixam de fora: não custa dinheiro e ainda assim tem um preço. Cada execução baixa seu banco de dados inteiro, e o Supabase mede esse download.

Pense nisso como a franquia de dados do seu plano de celular. Seu plano vem com uma cota mensal, tudo o que seu app faz puxa dela, e um backup é um download grande no mesmo plano. No plano gratuito, uma cópia noturna de um banco perto do limite de tamanho consome a cota do mês inteiro em uns dez dias. Isso, e uma connection string que o GitHub não alcança, são as duas coisas entre você e um backup que funciona, e nenhuma delas está na página do Supabase.

Dá para fazer backup do Supabase de graça com uma GitHub Action?

Dá, e o Supabase documenta como. A página dele sobre backups automáticos com GitHub Actions traz um workflow que instala a CLI do Supabase, exporta seus roles, seu schema e seus dados em três arquivos e faz commit deles de volta no repositório num horário fixo.

O GitHub também não cobra por isso, dentro de certos limites. Um repositório privado no GitHub Free ganha 2.000 minutos de Actions por mês, e um dump noturno de um banco pequeno usa uma parte pequena disso.

O que você ganha é uma cópia que sai do Supabase toda noite e vai parar nos servidores de outra empresa. É exatamente isso que os backups do próprio Supabase não conseguem te dar, porque as cópias diárias de um plano pago ficam dentro da conta que elas protegem.

É a resposta certa quando três coisas são verdade. Seu app já mora num repositório do GitHub, o que acontece se você ligou a sincronização com o GitHub no Lovable ou num builder parecido. Editar um arquivo YAML não te assusta. E seu banco é pequeno perto da cota de egress do seu plano. As duas primeiras você já sabe. A terceira é conta, e tem uma seção própria mais abaixo.

O workflow, com o horário e o secret

Salve isto como .github/workflows/supabase-backup.yml num repositório privado que não tenha mais nada:

name: supabase-backup

on:
  schedule:
    - cron: '17 3 * * *' # todo dia às 03:17 UTC
  workflow_dispatch:

jobs:
  backup:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    env:
      SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
    steps:
      - uses: actions/checkout@v7
      - uses: supabase/setup-cli@v3
        with:
          version: latest
      - name: Back up roles
        run: supabase db dump --db-url "$SUPABASE_DB_URL" -f roles.sql --role-only
      - name: Back up schema
        run: supabase db dump --db-url "$SUPABASE_DB_URL" -f schema.sql
      - name: Back up data
        run: >
          supabase db dump --db-url "$SUPABASE_DB_URL" -f data.sql --use-copy --data-only
          -x "storage.buckets_vectors" -x "storage.vector_indexes"
      - uses: stefanzweifel/git-auto-commit-action@v7
        with:
          commit_message: Supabase backup

É o workflow do Supabase com três mudanças.

  • Ele roda no horário e quando você aperta o botão, e em nenhum outro momento. A versão do Supabase também roda a cada push e a cada pull request para a main. Cada execução é um download completo do seu banco, então num repositório que também guarda o seu app, cada mudança que você publicasse gastaria a cota de um backup.
  • Ele roda às 03:17, fora da hora cheia. O GitHub diz que execuções agendadas podem atrasar no início de cada hora, e que com carga suficiente alguns jobs na fila são descartados. O exemplo do Supabase usa 0 0 * * *, que cai na hora cheia. Os horários são em UTC, a menos que você adicione um timezone, e qualquer minuto diferente de 0 serve.
  • A linha dos dados segue o guia atual de backup e restauração do Supabase, que acrescenta duas exclusões -x que a página do workflow não tem. Assim os arquivos que você guarda são aqueles para os quais as instruções de restauração do Supabase foram escritas.

Depois, o secret. No repositório, abra Settings, depois Secrets and variables, depois Actions, e adicione um secret de repositório chamado SUPABASE_DB_URL. O que vai dentro dele decide se tudo isso funciona, então ele ganha a próxima seção só para ele.

Depois de salvar, rode o workflow na mão pela aba Actions, para que a primeira execução aconteça enquanto você olha. workflow_dispatch é a linha que te dá o botão Run workflow.

Qual connection string vai no secret?

A string do Session pooler, pelo botão Connect no topo do seu projeto no Supabase. Use essa mesmo que a página do workflow do Supabase mostre a conexão direta no exemplo.

O motivo é o IPv6, o tipo mais novo de endereço de internet. A conexão direta do Supabase, o host que começa com db. e termina em supabase.co, usa IPv6 por padrão, e a mesma página lista o GitHub Actions entre os serviços que só aceitam IPv4. O runner recebe um endereço para o qual não tem caminho, e o primeiro dump falha antes de escrever um único byte. O erro diz que o nome do host não pôde ser traduzido, ou que a rede está inacessível, muitas vezes com um endereço longo cheio de dois-pontos ao lado. Esse endereço é o IPv6. Parece uma senha errada ou um banco fora do ar, e a causa real é uma rede em que o runner não está.

O guia de backup e restauração do Supabase diz para usar por padrão a string do Session pooler, e a direta só se a sua rede suporta IPv6 ou se você paga o add-on de IPv4. Você reconhece a string do pooler por três coisas: o nome de usuário é postgres. seguido da referência do seu projeto, o host termina em pooler.supabase.com e a porta é 5432.

O runner não tem caminho até o endereço IPv6 da conexão direta. O Session pooler responde por IPv4 e chega ao mesmo banco.

A senha dentro dela é a senha do seu banco de dados, a definida quando o projeto foi criado, e não uma chave de API. Se ninguém anotou, o Supabase deixa você redefini-la em Database Settings; qualquer outra coisa que se conecte com a antiga para de funcionar até você passar a nova.

Essa string consegue ler e alterar cada linha do seu banco. Ela vive no secret e em nenhum outro lugar: nunca no arquivo do workflow, nunca numa mensagem de commit e nunca colada num assistente de IA junto com o erro que você ia perguntar.

Um backup do Supabase conta no meu egress?

Conta, inteiro. O Supabase conta como egress, ou seja, tráfego de saída, os dados que qualquer um dos seus serviços manda para fora, e um dump pelo pooler entra como Shared Pooler Egress na mesma cota do seu tráfego de API, dos seus downloads de arquivos e de tudo o que o seu app entrega.

Voltando ao plano de celular. A cota zera a cada mês de cobrança, todo serviço que o seu app usa puxa dela, e um backup é mais um download grande. E é um plano família: o Supabase aplica a cota à sua organização inteira, então todos os projetos dela dividem a mesma cota.

Em 26 de setembro de 2026, a página de preços do Supabase dava ao plano gratuito 5 GB de egress por mês e um limite de 500 MB para o banco de cada projeto, e ao plano Pro 250 GB de egress. O plano gratuito tem mais 5 GB de cached egress, que cobre arquivos servidos pela CDN do Supabase, e um backup nunca toca nele. Os outros limites do plano gratuito, e o que cada um faz quando você passa dele, têm um artigo só deles.

Estourar a cota no plano gratuito não gera uma fatura. O Supabase avisa você e dá um período de carência, e se você continuar acima dela, ele restringe todos os projetos da organização. Segundo o Supabase, isso pode significar requisições de API respondidas com erro 402, um banco colocado em modo somente leitura ou projetos pausados. Para um app com clientes, é uma queda que o seu backup começou.

Isso vale para qualquer backup que sai do Supabase, inclusive o que nós vendemos. Uma cópia guardada fora da conta precisa ser baixada para chegar lá.

Com que frequência o backup deve rodar?

Com a frequência que a sua cota consegue pagar, e isso se resume a uma única multiplicação: o tamanho do seu banco vezes as execuções do mês.

O Supabase mostra o tamanho do seu banco no relatório Database, dentro de Observability no seu projeto. Um dump não tem exatamente esse tamanho. Ele deixa de fora os índices, que são reconstruídos a partir das definições, e escreve cada valor como texto. Chega perto o bastante para planejar, e é o número que você consegue consultar.

Tamanho do bancoDiário (30 execuções)Duas vezes por semanaSemanal
50 MB1,5 GB0,4 GB0,2 GB
100 MB3 GB0,9 GB0,4 GB
250 MB7,5 GB2,2 GB1,1 GB
500 MB15 GB4,3 GB2,2 GB

Um mês tem cerca de 4,3 semanas, e é daí que saem as duas últimas colunas.

No plano gratuito, os dois números diários de baixo gastam mais do que a cota do mês inteiro antes de o seu app responder uma única requisição. Um horário diário consome os 5 GB inteiros por volta de 160 MB, e o tráfego do seu próprio app sai da mesma cota, então veja o egress do mês passado na página Usage da sua organização antes de escolher. O semanal cabe em qualquer tamanho que o plano gratuito permite.

Um banco no limite de 500 MB do plano gratuito, com backup diário e semanal. A linha tracejada são os 5 GB de egress do plano gratuito no mês.

A linha do cron é a única coisa que você muda:

17 3 * * *      todo dia às 03:17 UTC
17 3 * * 1,4    segundas e quintas
17 3 * * 0      domingos

No Pro a conta quase deixa de importar. 250 GB por mês pagam um dump diário de um projeto de até uns 8 GB, que é o espaço em disco que o plano Pro inclui por projeto.

Por que meu workflow falha com um erro de versão do pg_dump?

Porque o pg_dump que ele rodou é mais antigo que o seu banco. Isso só acontece com um workflow que chama o pg_dump diretamente, e não pela CLI do Supabase. A CLI traz o próprio pg_dump, e esse é um dos motivos de o workflow acima usá-la.

O runner ubuntu-latest do GitHub vem com o PostgreSQL 16 instalado. Projetos do Supabase podem rodar em Postgres 17, e o Postgres documenta que o pg_dumpnão exporta de um servidor com versão principal mais nova que a dele, e prefere recusar a arriscar um arquivo defeituoso. A execução para com:

pg_dump: error: aborting because of server version mismatch
pg_dump: detail: server version: 17.…; pg_dump version: 16.…

A versão do seu projeto fica em Project Settings, depois General, se quiser confirmar. Não há nada de errado com as suas credenciais.

A correção tem dois passos. Adicione o repositório de pacotes do próprio PostgreSQL para que o runner consiga instalar o cliente da versão 17, e depois chame esse cliente pelo caminho completo:

- name: Install the Postgres 17 client
  run: |
    sudo apt-get install -y postgresql-common
    sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
    sudo apt-get install -y postgresql-client-17
- name: Back up
  run: /usr/lib/postgresql/17/bin/pg_dump "$SUPABASE_DB_URL" …

Mantenha depois da connection string os argumentos que você já tinha. O caminho completo importa: no Ubuntu, o pg_dump puro é um wrapper que escolhe uma versão por você, e o runner já tem uma instalação do PostgreSQL 16 para ele escolher.

O pg_dump da CLI também tem versão, e ela não vem do seu projeto. Sem um supabase/config.toml ao lado do workflow, que é o caso num repositório que não guarda mais nada, a CLI usa o pg_dump 17. Num projeto que ainda roda Postgres 15, o data.sql que ela escreve passa a ter SET transaction_timeout = 0, uma configuração que o Postgres 15 não tem, e a carga para nessa linha em qualquer banco Postgres 15, inclusive o projeto de onde o arquivo veio. Reproduzimos isso em 4 de outubro de 2026 com o pg_dump 17.11 e o Postgres 15.19. Se o seu projeto está no 15, acrescente uma linha ao bloco env do job para que a CLI use o pg_dump certo:

env:
  SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
  SUPABASE_DB_MAJOR_VERSION: '15'

O backup em si termina sem erro nos dois casos, então sem essa linha a diferença só aparece no dia em que você carrega o arquivo.

Posso fazer commit do backup no meu repositório?

Num privado, pode. É isso que o workflow do Supabase faz, e a página dele diz duas vezes para nunca fazer backup dos seus dados num repositório público.

Três coisas sobre um repositório como casa de um banco de dados, nenhuma delas naquela página:

  • Quem consegue ler o repositório consegue ler os seus usuários. O data.sql guarda cada linha: e-mails, nomes, o que as suas tabelas guardarem, e as suas contas também. Por isso o workflow ganha um repositório só dele, sem mais ninguém, e não o repositório do código do seu app, que o seu builder talvez sincronize e para o qual algum dia você talvez convide um freelancer.
  • A cópia de cada noite fica no histórico. O Git guarda cada versão de cada arquivo. Quando alguém da sua clientela pede para apagar a própria conta, a linha dessa pessoa continua em cada commit anterior daquele repositório até você reescrever o histórico dele.
  • Para de funcionar em 100 MiB. O GitHub avisa acima de 50 MiB e bloqueia arquivos acima de 100 MiB, e a mesma página diz que o Git não foi feito para lidar com arquivos SQL grandes. Na noite em que o data.sql passa desse limite, o passo de commit falha, e toda execução depois disso falha do mesmo jeito.

Quando você chegar perto, o mesmo workflow pode enviar os três arquivos para um bucket de armazenamento seu em vez de fazer commit deles. Ou é o ponto em que fazer isso você mesmo deixou de ser barato, que é onde começa a última seção.

O que o workflow não copia?

Os seus arquivos enviados. O Supabase Storage guarda cada arquivo fora do banco e uma linha que o descreve dentro, então o dump leva a linha e não a imagem. Fazer backup do Storage é um trabalho à parte, em qualquer caminho que você escolha.

Os seus usuários estão nele. O dump de dados percorre o schema auth, onde moram as suas contas, e procurar auth.users no data.sql mostra essas contas. O projeto em volta do banco não está: Edge Functions, configurações dos provedores de login, chaves de API e secrets são configuração e não dados, e a lista do que nenhuma das duas cópias guarda é a que vale ler antes de precisar dela.

Uma execução verde quer dizer que o backup funcionou?

Quer dizer que o job terminou sem erro, o que é uma afirmação menor. Três resultados bem diferentes terminam com o mesmo visto verde:

  1. Um bom dump do projeto certo.
  2. Um dump do projeto errado, porque o secret tem a string de uma cópia de staging.
  3. Um dump sem nenhuma linha, porque alguém editou a linha dos dados e o --data-only sumiu, o que a transforma de novo no dump do schema.

Um resultado não deixa marca nenhuma. Uma execução agendada que o GitHub descarta por carga não aparece como falha, porque não existe execução nenhuma para falhar. E quando uma execução falha de fato, o GitHub manda o aviso para a pessoa que editou a linha do cron por último. Se foi um freelancer que montou isso para você, o e-mail sobre o seu backup vai parar na caixa de entrada dele.

Os dois arquivos saíram de uma execução que terminou sem erro. Nada na execução diz qual deles você tem.

Só uma restauração separa um do outro. Rode os três arquivos num projeto descartável do Supabase ou num local, compare o número de linhas das tabelas que importam para você com as de verdade e entre como um usuário real. Como restaurar um backup do Supabase tem os comandos, na ordem que o Supabase dá. Faça isso uma vez agora, e de novo sempre que mudar o workflow.

Você também pode deixar cada execução cuidar da restauração e da contagem. Publicamos uma versão desse workflow como uma GitHub Action de código aberto, Reeve-page/supabase-backup-action. Ela faz os mesmos três dumps, guarda a cópia como artefato do workflow ou num bucket S3 ou R2, depois a reproduz num banco Supabase descartável no runner e compara o número de linhas de cada tabela com o arquivo. A execução só fica verde quando cada tabela voltou com as linhas que o arquivo tem:

- uses: Reeve-page/supabase-backup-action@v1
  with:
    db-url: ${{ secrets.SUPABASE_DB_URL }}

Ela é gratuita e tem licença MIT. Cada execução continua baixando o banco inteiro, então a conta de egress acima vale do mesmo jeito, e testar o login continua sendo com você.

Quando devo parar de fazer isso por conta própria?

Quando a conta para de fechar, quando o arquivo não cabe mais no repositório ou quando ninguém restaura as cópias. Qualquer uma delas basta.

  • Seu banco passou da cota gratuita. O Pro sobe o egress para 250 GB e acrescenta os backups diários do próprio Supabase, guardados por sete dias, que se restauram com um botão. Deixe o workflow rodando ao lado deles, porque essas cópias moram dentro da conta.
  • O data.sql está chegando a 100 MiB. Passar isso para um bucket é mais YAML, e mais coisa para alguém manter funcionando.
  • Ninguém nunca restaurou uma cópia. O workflow vai continuar escrevendo arquivos, abram eles ou não.
  • Seus usuários enviam arquivos. O workflow não chega até eles, e o job que chega é mais um para manter vivo.

Onde o Reeve Care entra

O Care faz a cópia num horário fixo, guarda fora da sua conta do Supabase e confere cada cópia antes de ela contar.

  • Todo dia no Care, e com mais frequência nos planos acima dele, sem nada no seu repositório e sem connection string numa CI.
  • Relida e contada. Cada cópia é aberta e contada contra o que entrou nela, e a data no seu painel é a da última cópia que passou nessa conferência, nunca a da última execução.
  • Suas contas estão dentro, e os seus arquivos enviados vêm junto assim que você conecta uma credencial do Storage.
  • Restaurar é um botão, e uma cópia do estado atual é feita antes de qualquer coisa ser substituída.
  • Qualquer cópia pode ser baixada como zip com schema.sql, data.sql e roles.sql, os mesmos três arquivos que este workflow faz, mais o número de linhas de cada tabela.

O Care guarda uma cópia do seu banco do Supabase. A cópia, a conferência e a restauração estão desenhadas passo a passo na página de backups do Supabase, e o que cada plano inclui está na página de preços.

O que fazer esta semana

O que fazer

  • Veja o tamanho do seu banco e o egress do mês passado, e escolha o horário pela tabela antes de escrever a linha do cron.
  • Crie um repositório privado que só tenha o workflow, e coloque a string do Session pooler no secret SUPABASE_DB_URL dele.
  • Se você começou pelo exemplo do Supabase, apague os gatilhos de push e pull request e tire o cron da hora cheia.
  • Rode uma vez na mão pela aba Actions, depois procure no data.sql por auth.users e por uma tabela que você sabe que tem linhas.
  • Restaure uma cópia num projeto descartável e entre como um usuário de verdade.
  • Copie os seus arquivos do Storage com um job à parte.

Antes de fechar esta aba, abra no Supabase a página Usage da sua organização e leia o egress do mês passado. Esse número, ao lado do tamanho do seu banco, é o que escolhe a sua linha do cron. E se você ainda está comparando o caminho gratuito com os pagos, os quatro tipos de ferramenta de backup do Supabase estão comparados lado a lado.

Perguntas frequentes

Posso fazer backup do Supabase de graça?

Pode. O Supabase documenta um workflow do GitHub Actions que instala a CLI dele, exporta seus roles, seu schema e seus dados em três arquivos num horário fixo e faz commit deles no repositório. Um repositório privado no GitHub Free ganha 2.000 minutos de Actions por mês, e um dump noturno de um banco pequeno usa uma parte pequena disso. O que o backup gasta de verdade é sua cota de egress no Supabase, porque cada execução baixa o banco inteiro.

Com que frequência um cron de backup do Supabase deve rodar?

Com a frequência que sua cota de egress consegue pagar. Multiplique o tamanho do seu banco pelas execuções do mês: um banco de 100 MB exportado todo dia move cerca de 3 GB, e um de 500 MB cerca de 15 GB. O plano gratuito inclui 5 GB para a organização inteira, divididos com o tráfego do seu próprio app. Coloque o job num minuto que não seja a hora cheia, porque o GitHub diz que execuções agendadas no início de cada hora podem atrasar, e algumas ser descartadas.

Um backup do Supabase conta no meu egress?

Conta. O Supabase conta como egress os dados que qualquer um dos seus serviços manda para fora, e um dump pelo connection pooler entra como Shared Pooler Egress na mesma cota do seu tráfego de API. A cota é da organização inteira, não de um projeto só. No plano gratuito, estourar essa cota repetidas vezes termina em restrições para todos os projetos da organização.

Por que meu workflow de backup falha na primeira execução?

Duas falhas são típicas desta montagem. A primeira é a connection string: a conexão direta do Supabase usa IPv6, a menos que você pague o add-on de IPv4, e o Supabase lista o GitHub Actions entre os serviços que só alcançam IPv4, então use a string do Session pooler. A segunda pega os workflows que chamam o pg_dump diretamente: o cliente PostgreSQL 16 do runner se recusa a exportar um projeto em Postgres 17 e para com aborting because of server version mismatch.

Posso fazer commit do meu backup do Supabase no meu repositório do GitHub?

Num privado, pode, e é isso que o workflow do Supabase faz. Num público, nunca, e o Supabase diz isso duas vezes na mesma página. Dê a ele um repositório só dele que ninguém mais consiga ler, porque o arquivo de dados guarda os e-mails dos seus usuários. E conte com ter que mudar quando o banco crescer: o GitHub avisa acima de 50 MiB e recusa arquivos acima de 100 MiB.

Como sei se o arquivo de backup presta?

Restaure. Uma execução verde significa que o job terminou sem erro, e um dump do projeto errado ou um dump sem nenhuma linha terminam do mesmo jeito. Rode os três arquivos num projeto descartável do Supabase ou num local, compare o número de linhas das tabelas que importam para você e entre como um usuário de verdade. Essa é a verificação que diz se o arquivo abre.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Não sabe como está o seu próprio app?

Faça um scan gratuito e receba uma nota clara de A a F em cerca de 20 segundos. Sem conta e sem cartão.

Verificar meu app grátis

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.