Backups
Backup gratis di Supabase con una GitHub Action, e il tranello
Un backup di Supabase con una GitHub Action è gratis e va bene per molte app. Il workflow, la connection string giusta e l'egress di ogni esecuzione.

In breve
- Una GitHub Action di backup di Supabase esegue la CLI di Supabase secondo un orario e fa il commit del dump in un repository privato. Non costa nulla, e per molte app è la risposta giusta.
- Ogni esecuzione scarica tutto il tuo database, e Supabase lo conta come egress. Il piano gratuito include 5 GB al mese per tutta l'organizzazione, e un dump giornaliero di un database vicino al limite di 500 MB ne consuma circa tre volte tanto.
- L'esempio della stessa Supabase si collega con una stringa che i runner di GitHub non riescono a raggiungere. Metti nel secret la stringa del Session pooler, e ripristina una copia prima di fidarti delle altre.
Il tuo progetto Supabase è sul piano gratuito, quindi niente viene copiato da nessuna parte, oppure è su Pro e ogni copia che fa vive dentro l'account che hai paura di perdere. In un thread proprio su questo, qualcuno ha detto che la risposta è una GitHub Action di backup di Supabase: un piccolo file in un repository che esporta il tuo database ogni notte e non costa nulla.
Il consiglio era buono. Supabase pubblica il workflow in prima persona, e per molte app è la cosa da mettere in piedi questa settimana. Ecco la parte che quei thread lasciano fuori: non costa denaro, eppure ha un prezzo. Ogni esecuzione scarica tutto il tuo database, e Supabase misura quel download.
Pensalo come i giga della tua offerta telefonica. Il piano ha una quota mensile, tutto quello che fa la tua app ci attinge, e un backup è un download pesante sulla stessa offerta. Sul piano gratuito, una copia notturna di un database vicino al suo limite di dimensione si mangia la quota di tutto il mese in una decina di giorni. Questo, e una connection string che GitHub non riesce a raggiungere, sono le due cose tra te e un backup che funziona, e nessuna delle due sta sulla pagina di Supabase.
Puoi fare il backup di Supabase gratis con una GitHub Action?
Sì, e Supabase documenta come. La sua pagina sui backup automatici con GitHub Actions fornisce un workflow che installa la CLI di Supabase, esporta ruoli, schema e dati in tre file e ne fa il commit nel repository secondo un orario.
Nemmeno GitHub ti fa pagare, entro certi limiti. Un repository privato su GitHub Free riceve 2.000 minuti di Actions al mese, e un dump notturno di un database piccolo ne usa una piccola parte.
Quello che ottieni è una copia che esce da Supabase ogni notte e atterra sui server di un'altra azienda. È proprio ciò che i backup di Supabase non possono darti, perché le copie giornaliere di un piano a pagamento restano dentro l'account che proteggono.
È la risposta giusta quando valgono tre cose. La tua app vive già in un repository GitHub, come succede se hai attivato la sincronizzazione con GitHub in Lovable o in un builder simile. Modificare un file YAML non ti preoccupa. E il tuo database è piccolo rispetto alla quota di egress del tuo piano. Le prime due le sai già. La terza è aritmetica, e ha una sezione tutta sua più sotto.
Il workflow, con l'orario e il secret
Salva questo come .github/workflows/supabase-backup.yml in un repository
privato che non contenga nient'altro:
name: supabase-backup
on:
schedule:
- cron: '17 3 * * *' # ogni giorno alle 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
È il workflow di Supabase con tre modifiche.
- Gira secondo l'orario e quando premi il pulsante, e in nessun altro
momento. La versione di Supabase gira anche a ogni push e a ogni pull
request su
main. Ogni esecuzione è un download completo del tuo database, quindi in un repository che contiene anche la tua app ogni modifica che pubblichi costerebbe la quota di un backup. - Gira alle 03:17, fuori dall'ora esatta. GitHub dice che le esecuzioni
programmate
possono essere ritardate all'inizio di ogni ora,
e che con un carico sufficiente alcuni job in coda vengono scartati. L'esempio
di Supabase usa
0 0 * * *, che cade allo scoccare dell'ora. Gli orari sono UTC a meno che tu non aggiunga untimezone, e va bene qualsiasi minuto diverso da0. - La riga dei dati corrisponde alla
guida attuale di Supabase su backup e ripristino,
che aggiunge due esclusioni
-xassenti dalla pagina del workflow. Così i file che conservi sono quelli per cui sono scritte le istruzioni di ripristino di Supabase.
Poi il secret. Nel repository apri Settings, poi Secrets and variables, poi
Actions, e aggiungi un secret di repository chiamato SUPABASE_DB_URL. Quello
che ci metti dentro decide se tutto questo funziona, quindi ha la prossima
sezione tutta per sé.
Una volta salvato, avvia il workflow a mano dalla scheda Actions, così la prima
esecuzione avviene mentre guardi. workflow_dispatch è la riga che ti dà il
pulsante Run workflow.
Quale connection string va nel secret?
La stringa del Session pooler, dal pulsante Connect in cima al tuo progetto Supabase. Usala anche se la pagina del workflow di Supabase mostra la connessione diretta nel suo esempio.
Il motivo è IPv6, il tipo più recente di indirizzo internet. La connessione
diretta di Supabase, l'host che comincia con db. e finisce con supabase.co,
usa IPv6 per impostazione predefinita,
e la stessa pagina mette GitHub Actions tra i servizi che accettano solo IPv4.
Il runner riceve un indirizzo verso cui non ha strada, e il primo dump fallisce
prima di scrivere un solo byte. L'errore dice che il nome host non si è potuto
tradurre, o che la rete non è raggiungibile, spesso con accanto un lungo
indirizzo pieno di due punti. Quell'indirizzo è quello IPv6. Sembra una password
sbagliata o un database giù, e la vera causa è una rete su cui il runner non si
trova.
La guida di Supabase su backup e ripristino dice di usare per impostazione
predefinita la stringa del Session pooler, e quella diretta solo se la tua rete
supporta IPv6 o se paghi l'add-on IPv4. Riconosci la stringa del pooler da tre
cose: il nome utente è postgres. seguito dal riferimento del tuo progetto,
l'host finisce con pooler.supabase.com e la porta è 5432.
La password al suo interno è la password del tuo database, quella impostata quando è stato creato il progetto, non una chiave API. Se nessuno l'ha annotata, Supabase ti permette di reimpostarla in Database Settings; qualsiasi altra cosa che si collega con quella vecchia smette di funzionare finché non le dai quella nuova.
Quella stringa può leggere e modificare ogni riga del tuo database. Vive nel secret e da nessun'altra parte: mai nel file del workflow, mai in un messaggio di commit e mai incollata in un assistente di IA insieme all'errore di cui volevi chiedere.
Un backup di Supabase conta contro il mio egress?
Sì, tutto quanto. Supabase conta come egress, cioè traffico in uscita, i dati che uno qualsiasi dei suoi servizi manda all'esterno, e un dump attraverso il pooler viene registrato come Shared Pooler Egress nella stessa quota del tuo traffico API, dei download di file e di tutto il resto che la tua app serve.
Torniamo all'offerta telefonica. La quota si azzera a ogni mese di fatturazione, ogni servizio che la tua app usa ci attinge, e un backup è un altro download pesante. È anche un piano famiglia: Supabase applica la quota a tutta la tua organizzazione, quindi tutti i progetti che contiene condividono la stessa quota.
Il 26 settembre 2026, la pagina dei prezzi di Supabase dava al piano gratuito 5 GB di egress al mese e un limite di 500 MB per il database di ogni progetto, e al piano Pro 250 GB di egress. Sul piano gratuito ci sono altri 5 GB di cached egress, che coprono i file serviti dalla CDN di Supabase, e un backup non li tocca mai. Gli altri limiti del piano gratuito, e cosa fa ognuno quando lo superi, hanno un articolo tutto loro.
Superare la quota sul piano gratuito non produce una fattura. Supabase ti avvisa e ti concede un periodo di tolleranza, e se continui a superarla limita ogni progetto dell'organizzazione. Secondo Supabase questo può voler dire richieste API respinte con un errore 402, un database messo in sola lettura o progetti in pausa. Per un'app con dei clienti, è un disservizio che ha avviato il tuo backup.
Vale per qualsiasi backup che esce da Supabase, compreso quello che vendiamo noi. Una copia tenuta fuori dall'account deve essere scaricata per arrivarci.
Ogni quanto dovrebbe girare il backup?
Tanto spesso quanto la tua quota può pagare, e tutto si riduce a una sola moltiplicazione: la dimensione del database per le esecuzioni del mese.
Supabase mostra la dimensione del tuo database nel report Database, sotto Observability nel tuo progetto. Un dump non è esattamente di quella dimensione. Lascia fuori gli indici, che vengono ricostruiti dalle loro definizioni, e scrive ogni valore come testo. È abbastanza vicino per pianificare, ed è il numero che puoi andare a vedere.
| Dimensione del database | Giornaliero (30 esecuzioni) | Due volte a settimana | Settimanale |
|---|---|---|---|
| 50 MB | 1,5 GB | 0,4 GB | 0,2 GB |
| 100 MB | 3 GB | 0,9 GB | 0,4 GB |
| 250 MB | 7,5 GB | 2,2 GB | 1,1 GB |
| 500 MB | 15 GB | 4,3 GB | 2,2 GB |
Un mese ha circa 4,3 settimane, ed è da lì che vengono le ultime due colonne.
Sul piano gratuito, le due cifre giornaliere in fondo consumano più della quota di tutto il mese prima che la tua app abbia risposto a una sola richiesta. Un orario giornaliero consuma tutti i 5 GB intorno ai 160 MB, e il traffico della tua app esce dalla stessa quota, quindi guarda l'egress del mese scorso nella pagina Usage della tua organizzazione prima di scegliere. Il settimanale ci sta con qualsiasi dimensione consentita dal piano gratuito.
La riga del cron è l'unica cosa che cambi:
17 3 * * * ogni giorno alle 03:17 UTC
17 3 * * 1,4 il lunedì e il giovedì
17 3 * * 0 la domenica
Su Pro l'aritmetica quasi smette di contare. 250 GB al mese pagano un dump giornaliero di un progetto fino a circa 8 GB, che è lo spazio su disco che il piano Pro include per progetto.
Perché il mio workflow fallisce con un errore di versione di pg_dump?
Perché il pg_dump che ha eseguito è più vecchio del tuo database. Succede
solo a un workflow che chiama pg_dump direttamente invece che attraverso la
CLI di Supabase. La CLI si porta il suo pg_dump, ed è uno dei motivi per cui
il workflow qui sopra la usa.
Il runner ubuntu-latest di GitHub arriva con
PostgreSQL 16 installato.
I progetti Supabase possono girare su Postgres 17, e Postgres documenta che
pg_dumpnon esporta da un server di una versione principale più recente della sua,
e preferisce rifiutarsi piuttosto che rischiare un file difettoso. L'esecuzione
si ferma con:
pg_dump: error: aborting because of server version mismatch
pg_dump: detail: server version: 17.…; pg_dump version: 16.…
La versione del tuo progetto è sotto Project Settings, poi General, se vuoi confermarla. Le tue credenziali non c'entrano.
La soluzione sono due passaggi. Aggiungi il repository dei pacchetti di PostgreSQL stesso, così il runner può installare il client della versione 17, poi chiama quel client con il suo percorso 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" …
Tieni dopo la connection string gli argomenti che avevi già. Il percorso
completo conta: su Ubuntu il semplice pg_dump è un wrapper che sceglie una
versione al posto tuo, e sul runner c'è già un'installazione di PostgreSQL 16
da scegliere.
Anche il pg_dump della CLI ha una versione, e non viene dal tuo progetto.
Senza un supabase/config.toml accanto al workflow, come in un repository che
non contiene altro, la CLI
usa pg_dump 17.
Su un progetto ancora su Postgres 15, il data.sql che scrive contiene allora
SET transaction_timeout = 0, un'impostazione che Postgres 15 non ha, e il
caricamento si ferma su quella riga in qualsiasi database Postgres 15, compreso
il progetto da cui viene il file. L'abbiamo riprodotto il 4 ottobre 2026 con
pg_dump 17.11 e Postgres 15.19. Se il tuo progetto è su 15, aggiungi una riga
al blocco env del job così la CLI usa il pg_dump corrispondente:
env:
SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
SUPABASE_DB_MAJOR_VERSION: '15'
Il backup in sé finisce senza errori in entrambi i casi, quindi senza quella riga la differenza si vede solo il giorno in cui ricarichi il file.
Posso fare il commit del backup nel mio repository?
In uno privato, sì. È quello che fa il workflow di Supabase, e la sua pagina dice due volte di non fare mai il backup dei dati in un repository pubblico.
Tre cose su un repository come casa di un database, nessuna delle quali sta su quella pagina:
- Chiunque può leggere il repository può leggere i tuoi utenti.
data.sqlcontiene ogni riga: indirizzi email, nomi, qualsiasi cosa tengano le tue tabelle, e anche i tuoi account. Per questo il workflow ha un repository tutto suo senza nessun altro dentro, e non quello in cui vive il codice della tua app, che il tuo builder magari sincronizza e in cui un giorno potresti invitare un freelance. - La copia di ogni notte resta nella cronologia. Git conserva ogni versione di ogni file. Quando qualcuno tra i tuoi clienti ti chiede di cancellare il proprio account, la sua riga resta in ogni commit precedente di quel repository finché non ne riscrivi la cronologia.
- Smette di funzionare a 100 MiB. GitHub avvisa sopra i 50 MiB e
blocca i file oltre i 100 MiB,
e la stessa pagina dice che Git non è pensato per gestire file SQL grandi. La
notte in cui
data.sqlsupera quella soglia, il passaggio di commit fallisce, e ogni esecuzione successiva fallisce allo stesso modo.
Quando ti avvicini, lo stesso workflow può caricare i tre file in un bucket di storage tuo invece di farne il commit. Oppure è il punto in cui farlo da te ha smesso di essere economico, ed è lì che comincia l'ultima sezione.
Cosa non copia il workflow?
I tuoi file caricati. Supabase Storage tiene ogni file fuori dal database e una riga che lo descrive dentro, quindi il dump porta con sé la riga e non l'immagine. Fare il backup di Storage è un lavoro a parte, su qualsiasi strada.
I tuoi utenti invece ci sono. Il dump dei dati attraversa lo schema auth in
cui vivono i tuoi account, e cercare auth.users in data.sql te li mostra. Il
progetto intorno al database no: Edge Functions, impostazioni dei provider di
autenticazione, chiavi API e secret sono configurazione e non dati, e
l'elenco di ciò che non c'è in nessuna delle due copie
è quello da leggere prima che ti serva.
Un'esecuzione verde vuol dire che il backup ha funzionato?
Vuol dire che il job è finito senza errori, che è un'affermazione più piccola. Tre risultati molto diversi finiscono con la stessa spunta verde:
- Un buon dump del progetto giusto.
- Un dump del progetto sbagliato, perché nel secret c'è la stringa di una copia di staging.
- Un dump senza nessuna riga, perché qualcuno ha modificato la riga dei dati e
--data-onlyè sparito, il che la trasforma di nuovo nel dump dello schema.
Un risultato non lascia alcuna traccia. Un'esecuzione programmata che GitHub scarta sotto carico non compare come fallimento, perché non c'è nessuna esecuzione che possa fallire. E quando un'esecuzione fallisce davvero, GitHub manda la notifica alla persona che ha modificato per ultima la riga del cron. Se te l'ha configurato un freelance, l'email sul tuo backup arriva nella sua casella.
Solo un ripristino li distingue. Riproduci i tre file in un progetto Supabase usa e getta o in uno locale, confronta il numero di righe delle tabelle che ti interessano con quelle vere ed entra come un utente vero. Come ripristinare un backup di Supabase ha i comandi, nell'ordine indicato da Supabase. Fallo una volta adesso, e di nuovo ogni volta che cambi il workflow.
Puoi anche lasciare il ripristino e il conteggio a ogni esecuzione. Abbiamo pubblicato una versione di questo workflow come una GitHub Action open source, Reeve-page/supabase-backup-action. Fa gli stessi tre dump, conserva la copia come artefatto del workflow o in un bucket S3 o R2, poi la riproduce in un database Supabase usa e getta sul runner e confronta il numero di righe di ogni tabella con il file. L'esecuzione diventa verde solo quando ogni tabella è tornata con le righe che il file contiene:
- uses: Reeve-page/supabase-backup-action@v1
with:
db-url: ${{ secrets.SUPABASE_DB_URL }}
È gratuita e con licenza MIT. Ogni esecuzione scarica comunque l'intero database, quindi il calcolo dell'egress qui sopra vale identico, e provare l'accesso resta compito tuo.
Quando dovrei smettere di farlo per conto mio?
Quando l'aritmetica non torna più, quando il file non ci sta più nel repository o quando nessuno ripristina le copie. Ne basta una.
- Il tuo database ha superato la quota gratuita. Pro porta l'egress a 250 GB e aggiunge i backup giornalieri di Supabase, conservati sette giorni, che si ripristinano con un pulsante. Lascia il workflow attivo accanto a loro, perché quelle copie vivono dentro l'account.
data.sqlsi avvicina ai 100 MiB. Spostarlo in un bucket vuol dire più YAML, e più cose che qualcuno deve tenere in funzione.- Nessuno ha mai ripristinato una copia. Il workflow continuerà a scrivere file, che si aprano o no.
- I tuoi utenti caricano file. Il workflow non li raggiunge, e il job che li raggiunge è un secondo job da tenere in vita.
Dove si inserisce Reeve Care
Care fa la copia secondo un orario, la tiene fuori dal tuo account Supabase e controlla ogni copia prima che conti.
- Ogni giorno con Care, e più spesso con i piani superiori, senza niente nel tuo repository e senza connection string in una CI.
- Riletta e contata. Ogni copia viene aperta e contata rispetto a ciò che ci è entrato, e la data nella tua dashboard è quella dell'ultima copia che ha superato il controllo, mai quella dell'ultima esecuzione.
- I tuoi account ci sono, e anche i tuoi file caricati arrivano non appena colleghi una credenziale di Storage.
- Ripristinare è un pulsante, e prima di sostituire qualsiasi cosa viene fatta una copia dello stato attuale.
- Ogni copia si scarica come zip con
schema.sql,data.sqleroles.sql, gli stessi tre file che fa questo workflow, più il numero di righe di ogni tabella.
Care conserva una copia del tuo database Supabase. La copia, il controllo e il ripristino sono disegnati passo per passo nella pagina sui backup di Supabase, e cosa include ogni piano è nella pagina dei prezzi.
Cosa fare questa settimana
Cosa fare
- Controlla la dimensione del tuo database e l'egress del mese scorso, e scegli l'orario dalla tabella prima di scrivere la riga del cron.
- Crea un repository privato che contenga solo il workflow, e metti la stringa del Session pooler nel suo secret
SUPABASE_DB_URL. - Se hai cominciato dall'esempio di Supabase, cancella i trigger di push e pull request e sposta il cron fuori dall'ora esatta.
- Avvialo una volta a mano dalla scheda Actions, poi cerca in
data.sqlauth.userse una tabella che sai avere delle righe. - Ripristina una copia in un progetto usa e getta ed entra come un utente vero.
- Copia i tuoi file di Storage con un job a parte.
Prima di chiudere questa scheda, apri in Supabase la pagina Usage della tua organizzazione e leggi l'egress del mese scorso. Quella cifra, accanto alla dimensione del tuo database, sceglie la tua riga del cron. E se stai ancora confrontando la strada gratuita con quelle a pagamento, i quattro tipi di strumento di backup per Supabase sono messi a confronto uno accanto all'altro.
FAQ
Posso fare il backup di Supabase gratis?
Sì. Supabase documenta un workflow di GitHub Actions che installa la sua CLI, esporta ruoli, schema e dati in tre file secondo un orario e ne fa il commit nel repository. Un repository privato su GitHub Free riceve 2.000 minuti di Actions al mese, e un dump notturno di un database piccolo ne usa una piccola parte. Quello che il backup consuma davvero è la tua quota di egress su Supabase, perché ogni esecuzione scarica l'intero database.
Ogni quanto dovrebbe girare un cron di backup di Supabase?
Tanto spesso quanto la tua quota di egress può pagare. Moltiplica la dimensione del database per le esecuzioni del mese: un database da 100 MB esportato ogni giorno sposta circa 3 GB, uno da 500 MB circa 15 GB. Il piano gratuito include 5 GB per tutta l'organizzazione, condivisi con il traffico della tua app. Metti il job su un minuto che non sia allo scoccare dell'ora, perché GitHub dice che le esecuzioni programmate all'inizio di ogni ora possono essere ritardate, e alcune scartate.
Un backup di Supabase conta contro il mio egress?
Sì. Supabase conta come egress i dati che uno qualsiasi dei suoi servizi manda all'esterno, e un dump attraverso il connection pooler viene registrato come Shared Pooler Egress, nella stessa quota del tuo traffico API. La quota appartiene a tutta l'organizzazione, non a un singolo progetto. Sul piano gratuito, superarla più volte porta a restrizioni su ogni progetto dell'organizzazione.
Perché il mio workflow di backup fallisce alla prima esecuzione?
Ci sono due errori tipici di questa configurazione. Il primo è la connection string: la connessione diretta di Supabase usa IPv6 a meno che tu non paghi l'add-on IPv4, e Supabase mette GitHub Actions tra i servizi che raggiungono solo IPv4, quindi usa la stringa del Session pooler. Il secondo colpisce i workflow che chiamano pg_dump direttamente: il client PostgreSQL 16 del runner si rifiuta di esportare un progetto su Postgres 17 e si ferma con aborting because of server version mismatch.
Posso fare il commit del mio backup di Supabase nel mio repository GitHub?
In uno privato sì, ed è quello che fa il workflow di Supabase. In uno pubblico mai, e Supabase lo dice due volte nella stessa pagina. Dagli un repository tutto suo che nessun altro possa leggere, perché il file dei dati contiene gli indirizzi email dei tuoi utenti. E mettiti in conto di spostarlo quando il database cresce: GitHub avvisa sopra i 50 MiB e rifiuta i file oltre i 100 MiB.
Come faccio a sapere se il file di backup è buono?
Ripristinalo. Un'esecuzione verde significa che il job è finito senza errori, e un dump del progetto sbagliato o un dump senza nessuna riga finiscono allo stesso modo. Riproduci i tre file in un progetto Supabase usa e getta o in uno locale, confronta il numero di righe delle tabelle che ti interessano ed entra come un utente vero. È questo il controllo che ti dice se il file si apre.