Backups
Backup gratis de Supabase con una GitHub Action, y la trampa
Un backup de Supabase con GitHub Action es gratis y sirve a muchas apps. El workflow, el connection string correcto y el egress de cada ejecución.

En resumen
- Una GitHub Action de backup de Supabase ejecuta la CLI de Supabase según un horario y hace commit del dump en un repositorio privado. No cuesta nada, y para muchas apps es la respuesta correcta.
- Cada ejecución descarga tu base de datos entera, y Supabase lo cuenta como egress. El plan gratuito incluye 5 GB al mes para toda la organización, y un dump diario de una base cerca del límite de 500 MB gasta unas tres veces eso.
- El ejemplo del propio Supabase se conecta con un string que los runners de GitHub no alcanzan. Pon el string del Session pooler en el secret y restaura una copia antes de fiarte del resto.
Tu proyecto de Supabase está en el plan gratuito, así que nada se copia a ninguna parte, o está en Pro y cada copia que hace vive dentro de la cuenta que te preocupa perder. En un hilo justo sobre eso, alguien dijo que la respuesta es una GitHub Action de backup de Supabase: un archivo pequeño en un repositorio que vuelca tu base de datos cada noche y no cuesta nada.
El consejo era bueno. Supabase publica el workflow él mismo, y para muchas apps es lo que conviene montar esta semana. Esta es la parte que esos hilos se saltan: no cuesta dinero y aun así tiene un precio. Cada ejecución descarga tu base de datos entera, y Supabase mide esa descarga.
Piénsalo como los datos de tu plan de móvil. Tu plan trae un cupo mensual, todo lo que hace tu app tira de él, y un backup es una descarga grande en el mismo plan. En el plan gratuito, una copia nocturna de una base cerca de su límite de tamaño se come el cupo de todo el mes en unos diez días. Eso, y un connection string que GitHub no alcanza, son las dos cosas entre tú y un backup que funcione, y ninguna de las dos está en la página de Supabase.
¿Puedes hacer backup de Supabase gratis con una GitHub Action?
Sí, y Supabase documenta cómo. Su página sobre backups automáticos con GitHub Actions da un workflow que instala la CLI de Supabase, vuelca tus roles, tu esquema y tus datos en tres archivos y les hace commit de vuelta en el repositorio según un horario.
GitHub tampoco cobra por ello, dentro de unos límites. Un repositorio privado en GitHub Free recibe 2.000 minutos de Actions al mes, y un dump nocturno de una base pequeña gasta una parte pequeña de ellos.
Lo que obtienes es una copia que sale de Supabase cada noche y aterriza en los servidores de otra empresa. Eso es justo lo que los backups del propio Supabase no pueden darte, porque las copias diarias de un plan de pago se quedan dentro de la cuenta que protegen.
Es la respuesta correcta cuando se cumplen tres cosas. Tu app ya vive en un repositorio de GitHub, que es lo que pasa si activaste la sincronización con GitHub en Lovable o en un builder parecido. Editar un archivo YAML no te preocupa. Y tu base de datos es pequeña al lado del cupo de egress de tu plan. Las dos primeras ya las sabes. La tercera es aritmética, y tiene su propia sección más abajo.
El workflow, con el horario y el secret
Guarda esto como .github/workflows/supabase-backup.yml en un repositorio
privado que no contenga nada más:
name: supabase-backup
on:
schedule:
- cron: '17 3 * * *' # cada día a las 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
Es el workflow de Supabase con tres cambios.
- Se ejecuta según el horario y cuando pulsas el botón, y en ningún otro
momento. La versión de Supabase también se ejecuta con cada push y cada pull
request a
main. Cada ejecución es una descarga completa de tu base, así que en un repositorio que además contiene tu app, cada cambio que publicaras gastaría el cupo de un backup. - Se ejecuta a las 03:17, fuera de la hora en punto. GitHub dice que las
ejecuciones programadas
pueden retrasarse al inicio de cada hora,
y que con suficiente carga algunos trabajos en cola se descartan. El ejemplo de
Supabase usa
0 0 * * *, que es una hora en punto. Las horas son UTC salvo que añadas untimezone, y cualquier minuto distinto de0sirve. - La línea de datos coincide con la
guía de backup y restauración
actual de Supabase, que añade dos exclusiones
-xque la página del workflow no tiene. Así los archivos que guardas son los que esperan las instrucciones de restauración de Supabase.
Luego el secret. En el repositorio, abre Settings, luego Secrets and variables,
luego Actions, y añade un secret de repositorio llamado SUPABASE_DB_URL. Lo
que va dentro decide si todo esto funciona, así que tiene la siguiente sección
para él solo.
Cuando esté guardado, ejecuta el workflow a mano desde la pestaña Actions, para
que la primera ejecución ocurra mientras miras. workflow_dispatch es la línea
que te da el botón Run workflow.
¿Qué connection string va en el secret?
El string del Session pooler, desde el botón Connect en la parte de arriba de tu proyecto de Supabase. Úsalo aunque la página del workflow de Supabase muestre la conexión directa en su ejemplo.
El motivo es IPv6, el tipo más nuevo de dirección de internet. La conexión
directa de Supabase, el host que empieza por db. y termina en supabase.co,
usa IPv6 por defecto,
y la misma página incluye GitHub Actions entre los servicios que solo aceptan
IPv4. El runner recibe una dirección a la que no tiene camino, y el primer dump
falla antes de escribir un solo byte. El error dice que el nombre del host no se
pudo traducir, o que la red es inalcanzable, a menudo con una dirección larga
llena de dos puntos al lado. Esa dirección es la IPv6. Parece una contraseña
equivocada o una base caída, y la causa real es una red en la que el runner no
está.
La guía de backup y restauración de Supabase dice que uses por defecto el string
del Session pooler, y el directo solo si tu red admite IPv6 o pagas el
complemento IPv4. El string del pooler se reconoce por tres cosas: su nombre de
usuario es postgres. seguido de la referencia de tu proyecto, su host termina
en pooler.supabase.com y su puerto es 5432.
La contraseña que lleva es la contraseña de tu base de datos, la que se puso al crear el proyecto, y no una clave de API. Si nadie la apuntó, Supabase te deja restablecerla en Database Settings; cualquier otra cosa que se conecte con la antigua deja de funcionar hasta que le des la nueva.
Ese string puede leer y cambiar cada fila de tu base de datos. Vive en el secret y en ningún otro sitio: nunca en el archivo del workflow, nunca en un mensaje de commit y nunca pegado en un asistente de IA junto con el error por el que ibas a preguntar.
¿Un backup de Supabase cuenta contra mi egress?
Sí, entero. Supabase cuenta como egress, es decir, tráfico de salida, los datos que cualquiera de sus servicios envía hacia fuera, y un dump a través del pooler se registra como Shared Pooler Egress dentro del mismo cupo que tu tráfico de API, tus descargas de archivos y todo lo demás que sirve tu app.
Volvamos al plan de móvil. El cupo se reinicia cada mes de facturación, cada servicio que usa tu app tira de él, y un backup es una descarga grande más. Además es un plan familiar: Supabase aplica el cupo a toda tu organización, así que todos los proyectos que hay en ella comparten el mismo cupo.
El 26 de septiembre de 2026, la página de precios de Supabase daba al plan gratuito 5 GB de egress al mes y un límite de 500 MB para la base de datos de cada proyecto, y al plan Pro 250 GB de egress. En el plan gratuito hay otros 5 GB de cached egress, que cubre los archivos servidos desde la CDN de Supabase, y un backup nunca lo toca. Los demás límites del plan gratuito, y lo que hace cada uno cuando lo cruzas, tienen su propio artículo.
Pasarse del cupo en el plan gratuito no genera una factura. Supabase te avisa y te da un periodo de gracia, y si sigues pasándote, restringe todos los proyectos de la organización. Dice que eso puede significar peticiones a la API respondidas con un error 402, una base de datos pasada a solo lectura o proyectos pausados. Para una app con clientes, es una caída que empezó tu backup.
Esto vale para cualquier backup que salga de Supabase, incluido el que vendemos nosotros. Una copia guardada fuera de la cuenta tiene que descargarse para llegar allí.
¿Cada cuánto debería ejecutarse el backup?
Tan a menudo como tu cupo pueda pagar, y eso se reduce a una sola multiplicación: el tamaño de tu base de datos por las ejecuciones del mes.
Supabase muestra el tamaño de tu base en el informe Database, dentro de Observability en tu proyecto. Un dump no mide exactamente eso. Deja fuera los índices, que se reconstruyen a partir de sus definiciones, y escribe cada valor como texto. Se acerca lo bastante para planificar, y es el número que puedes consultar.
| Tamaño de la base | Diario (30 ejecuciones) | Dos veces por semana | Semanal |
|---|---|---|---|
| 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 mes tiene unas 4,3 semanas, y de ahí salen las dos últimas columnas.
En el plan gratuito, las dos cifras diarias de abajo gastan más que el cupo de todo el mes antes de que tu app haya respondido una sola petición. Un horario diario gasta los 5 GB completos con unos 160 MB, y el tráfico de tu propia app sale del mismo cupo, así que mira el egress del mes pasado en la página Usage de tu organización antes de elegir. El semanal cabe con cualquier tamaño que permita el plan gratuito.
La línea de cron es lo único que cambias:
17 3 * * * cada día a las 03:17 UTC
17 3 * * 1,4 lunes y jueves
17 3 * * 0 domingos
En Pro la aritmética casi deja de importar. 250 GB al mes pagan un dump diario de un proyecto de hasta unos 8 GB, que es el espacio en disco que el plan Pro incluye por proyecto.
¿Por qué falla mi workflow con un error de versión de pg_dump?
Porque el pg_dump que ejecutó es más antiguo que tu base de datos. Eso solo le
pasa a un workflow que llama a pg_dump directamente en lugar de hacerlo a
través de la CLI de Supabase. La CLI trae su propio pg_dump, y esa es una de
las razones por las que el workflow de arriba la usa.
El runner ubuntu-latest de GitHub viene con
PostgreSQL 16 instalado.
Los proyectos de Supabase pueden usar Postgres 17, y Postgres documenta que
pg_dumpno vuelca desde un servidor de una versión mayor más nueva que la suya,
y prefiere negarse a arriesgarse a un archivo defectuoso. La ejecución se
detiene con:
pg_dump: error: aborting because of server version mismatch
pg_dump: detail: server version: 17.…; pg_dump version: 16.…
La versión de tu proyecto está en Project Settings, luego General, si quieres confirmarla. No hay nada mal en tus credenciales.
El arreglo son dos pasos. Añade el repositorio de paquetes del propio PostgreSQL para que el runner pueda instalar el cliente de la versión 17, y luego llama a ese cliente por su ruta completa:
- 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" …
Mantén después del connection string los argumentos que ya tenías. La ruta
completa importa: en Ubuntu, el pg_dump a secas es un envoltorio que elige una
versión por ti, y el runner ya tiene una instalación de PostgreSQL 16 para que
la elija.
El pg_dump de la CLI también tiene versión, y no sale de tu proyecto. Sin un
supabase/config.toml junto al workflow, que es lo que pasa en un repositorio
que no tiene nada más, la CLI
usa pg_dump 17.
En un proyecto que sigue en Postgres 15, el data.sql que escribe lleva
entonces SET transaction_timeout = 0, un ajuste que Postgres 15 no tiene, y la
carga se detiene en esa línea en cualquier base de datos Postgres 15, incluido
el proyecto del que salió el archivo. Lo reprodujimos el 4 de octubre de 2026
con pg_dump 17.11 y Postgres 15.19. Si tu proyecto está en 15, añade una línea
al bloque env del job para que la CLI use el pg_dump que corresponde:
env:
SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
SUPABASE_DB_MAJOR_VERSION: '15'
El backup en sí termina sin errores en los dos casos, así que sin esa línea la diferencia solo aparece el día que cargas el archivo.
¿Puedo hacer commit del backup en mi repositorio?
En uno privado, sí. Es lo que hace el workflow de Supabase, y su página dice dos veces que nunca hagas backup de tus datos en un repositorio público.
Tres cosas sobre un repositorio como hogar de una base de datos, ninguna de ellas en esa página:
- Quien pueda leer el repositorio puede leer a tus usuarios.
data.sqlcontiene cada fila: direcciones de correo, nombres, lo que guarden tus tablas, y también tus cuentas. Por eso el workflow tiene un repositorio propio sin nadie más dentro, y no el de tu app, que tu builder puede sincronizar y al que algún día quizá invites a un freelance. - La copia de cada noche se queda en el historial. Git guarda cada versión de cada archivo. Cuando alguien de tu clientela te pide borrar su cuenta, su fila sigue en cada commit anterior de ese repositorio hasta que reescribas su historial.
- Deja de funcionar a los 100 MiB. GitHub avisa con archivos de más de
50 MiB y
bloquea los de más de 100 MiB,
y la misma página dice que Git no está pensado para manejar archivos SQL
grandes. La noche en que
data.sqlcruza esa línea, el paso de commit falla, y todas las ejecuciones siguientes fallan igual.
Cuando te acerques, el mismo workflow puede subir los tres archivos a un bucket de almacenamiento tuyo en lugar de hacer commit de ellos. O es el punto en que hacerlo tú mismo ha dejado de salir barato, que es donde empieza la última sección.
¿Qué no copia el workflow?
Tus archivos subidos. Supabase Storage guarda cada archivo fuera de la base de datos y una fila que lo describe dentro, así que el dump lleva la fila y no la imagen. Hacer backup de Storage es un trabajo aparte, en cualquier camino que elijas.
Tus usuarios sí están. El dump de datos recorre el esquema auth donde viven
tus cuentas, y buscar auth.users en data.sql te los muestra. El proyecto
alrededor de la base de datos no está: Edge Functions, ajustes de proveedores de
auth, claves de API y secrets son configuración y no datos, y
la lista de lo que no guarda ninguna de las dos copias
es la que conviene leer antes de necesitarla.
¿Una ejecución en verde significa que el backup funcionó?
Significa que el trabajo terminó sin errores, que es una afirmación más pequeña. Tres resultados muy distintos acaban con la misma marca verde:
- Un buen dump del proyecto correcto.
- Un dump del proyecto equivocado, porque el secret tiene el string de una copia de staging.
- Un dump sin ninguna fila, porque alguien editó la línea de datos y se perdió
--data-only, lo que la vuelve a convertir en el dump del esquema.
Un resultado no deja ninguna huella. Una ejecución programada que GitHub descarta por carga no aparece como fallo, porque no hay ninguna ejecución que pueda fallar. Y cuando una ejecución sí falla, GitHub envía el aviso a la persona que editó por última vez la línea de cron. Si te lo montó un freelance, el correo sobre tu backup llega a su bandeja.
Solo una restauración los distingue. Reproduce los tres archivos en un proyecto de Supabase desechable o en uno local, compara el número de filas de las tablas que te importan con las reales e inicia sesión como un usuario real. Cómo restaurar un backup de Supabase tiene los comandos, en el orden que da Supabase. Hazlo una vez ahora, y otra cada vez que cambies el workflow.
También puedes dejar que cada ejecución se encargue de la restauración y del recuento. Publicamos una versión de este workflow como una GitHub Action de código abierto, Reeve-page/supabase-backup-action. Hace los mismos tres dumps, guarda la copia como artefacto del workflow o en un bucket de S3 o R2, después la reproduce en una base de datos de Supabase desechable en el runner y compara el número de filas de cada tabla con el archivo. La ejecución solo sale en verde cuando cada tabla volvió con las filas que tiene el archivo:
- uses: Reeve-page/supabase-backup-action@v1
with:
db-url: ${{ secrets.SUPABASE_DB_URL }}
Es gratuita y tiene licencia MIT. Cada ejecución sigue descargando la base de datos entera, así que el cálculo de egress de arriba se le aplica tal cual, y probar el inicio de sesión sigue siendo cosa tuya.
¿Cuándo debería dejar de hacerlo yo mismo?
Cuando la aritmética deja de cuadrar, cuando el archivo ya no cabe en el repositorio o cuando nadie restaura las copias. Basta con una de las tres.
- Tu base de datos ha superado el cupo gratuito. Pro sube el egress a 250 GB y añade los backups diarios del propio Supabase, guardados siete días, que se restauran con un botón. Deja el workflow funcionando a su lado, porque esas copias viven dentro de la cuenta.
data.sqlse acerca a 100 MiB. Pasarlo a un bucket es más YAML, y más cosas que alguien tiene que mantener funcionando.- Nadie ha restaurado nunca una copia. El workflow seguirá escribiendo archivos, se abran o no.
- Tus usuarios suben archivos. El workflow no llega a ellos, y el trabajo que sí llega es otro más que mantener vivo.
Dónde encaja Reeve Care
Care hace la copia según un horario, la guarda fuera de tu cuenta de Supabase y comprueba cada copia antes de que cuente.
- A diario en Care, y más a menudo en los planes superiores, sin nada en tu repositorio y sin ningún connection string en una CI.
- Releída y contada. Cada copia se abre y se cuenta frente a lo que entró, y la fecha de tu panel es la de la última copia que pasó esa comprobación, nunca la de la última ejecución.
- Tus cuentas van dentro, y tus archivos subidos también vienen en cuanto conectas una credencial de Storage.
- Restaurar es un botón, y antes de reemplazar nada se hace una copia del estado actual.
- Cualquier copia se descarga como zip con
schema.sql,data.sqlyroles.sql, los mismos tres archivos que hace este workflow, más el número de filas de cada tabla.
Care guarda una copia de tu base de datos de Supabase. La copia, la comprobación y la restauración están dibujadas paso a paso en la página de backups de Supabase, y qué incluye cada plan está en la página de precios.
Qué hacer esta semana
Qué hacer
- Consulta el tamaño de tu base de datos y el egress del mes pasado, y elige el horario con la tabla antes de escribir la línea de cron.
- Crea un repositorio privado que no contenga más que el workflow, y pon el string del Session pooler en su secret
SUPABASE_DB_URL. - Si empezaste con el ejemplo de Supabase, borra los disparadores de push y pull request y saca el cron de la hora en punto.
- Ejecútalo una vez a mano desde la pestaña Actions y busca en
data.sqlauth.usersy una tabla que sepas que tiene filas. - Restaura una copia en un proyecto desechable e inicia sesión como un usuario real.
- Copia tus archivos de Storage con un trabajo aparte.
Antes de cerrar esta pestaña, abre en Supabase la página Usage de tu organización y lee el egress del mes pasado. Esa cifra, al lado del tamaño de tu base de datos, es la que elige tu línea de cron. Y si todavía estás comparando el camino gratuito con los de pago, los cuatro tipos de herramienta de backup de Supabase están comparados uno al lado del otro.
Preguntas frecuentes
¿Puedo hacer backup de Supabase gratis?
Sí. Supabase documenta un workflow de GitHub Actions que instala su CLI, vuelca tus roles, tu esquema y tus datos en tres archivos según un horario y les hace commit en el repositorio. Un repositorio privado en GitHub Free recibe 2.000 minutos de Actions al mes, y un dump nocturno de una base pequeña gasta una parte pequeña de ellos. Lo que el backup sí gasta es tu cupo de egress en Supabase, porque cada ejecución descarga la base entera.
¿Cada cuánto debería ejecutarse un cron de backup de Supabase?
Tan a menudo como tu cupo de egress pueda pagar. Multiplica el tamaño de tu base por las ejecuciones del mes: una base de 100 MB volcada a diario mueve unos 3 GB, y una de 500 MB unos 15 GB. El plan gratuito incluye 5 GB para toda la organización, compartidos con el tráfico de tu propia app. Pon el trabajo en un minuto que no sea la hora en punto, porque GitHub dice que las ejecuciones programadas al inicio de cada hora pueden retrasarse, y algunas descartarse.
¿Un backup de Supabase cuenta contra mi egress?
Sí. Supabase cuenta como egress los datos que cualquiera de sus servicios envía hacia fuera, y un dump a través del connection pooler se registra como Shared Pooler Egress dentro del mismo cupo que tu tráfico de API. El cupo pertenece a toda la organización, no a un solo proyecto. En el plan gratuito, pasarse de él una y otra vez termina en restricciones para todos los proyectos de la organización.
¿Por qué falla mi workflow de backup en la primera ejecución?
Hay dos fallos propios de este montaje. El primero es el connection string: la conexión directa de Supabase usa IPv6 salvo que pagues el complemento IPv4, y Supabase incluye GitHub Actions entre los servicios que solo llegan a IPv4, así que usa el string del Session pooler. El segundo afecta a los workflows que llaman a pg_dump directamente: el cliente PostgreSQL 16 del runner se niega a volcar un proyecto en Postgres 17 y se detiene con aborting because of server version mismatch.
¿Puedo hacer commit de mi backup de Supabase en mi repositorio de GitHub?
En uno privado, sí, que es lo que hace el workflow de Supabase. En uno público nunca, y Supabase lo dice dos veces en la misma página. Dale un repositorio propio que nadie más pueda leer, porque el archivo de datos contiene las direcciones de correo de tus usuarios. Y cuenta con tener que moverlo cuando la base crezca: GitHub avisa con archivos de más de 50 MiB y rechaza los de más de 100 MiB.
¿Cómo sé si el archivo de backup sirve?
Restáuralo. Una ejecución en verde significa que el trabajo terminó sin errores, y un dump del proyecto equivocado o un dump sin ninguna fila terminan igual. Reproduce los tres archivos en un proyecto de Supabase desechable o en uno local, compara el número de filas de las tablas que te importan e inicia sesión como un usuario real. Esa es la comprobación que te dice si el archivo se abre.