Backups
Безкоштовна резервна копія Supabase через GitHub Action і підступ
Резервна копія Supabase через GitHub Action безкоштовна й годиться багатьом застосункам. Робочий процес, потрібний рядок підключення та egress.

Коротко
- GitHub Action для резервної копії Supabase запускає CLI Supabase за розкладом і комітить дамп у приватний репозиторій. Вона нічого не коштує, і для багатьох застосунків це правильна відповідь.
- Кожен запуск завантажує всю вашу базу даних, і Supabase рахує це як egress. Безкоштовний тариф дає 5 ГБ на місяць на всю організацію, а щоденний дамп бази, близької до ліміту 500 МБ, витрачає приблизно втричі більше.
- Приклад від самого Supabase підключається рядком, до якого раннери GitHub не можуть дістатися. Покладіть у секрет рядок Session pooler і відновіть одну копію, перш ніж довіряти решті.
Ваш проєкт Supabase на безкоштовному тарифі, тож нічого нікуди не копіюється, або він на Pro, і кожна копія, яку той робить, живе всередині облікового запису, втрати якого ви боїтеся. В обговоренні саме про це хтось сказав, що відповідь це GitHub Action для резервної копії Supabase: невеликий файл у репозиторії, який щоночі вивантажує вашу базу даних і нічого не коштує.
Порада була слушна. Supabase сам публікує цей робочий процес, і для багатьох застосунків це саме те, що варто налаштувати цього тижня. Ось частина, яку такі обговорення пропускають: це не коштує грошей, і все одно має ціну. Кожен запуск завантажує всю вашу базу даних, і Supabase цей обсяг рахує.
Уявіть пакет інтернету у вашому мобільному тарифі. Тариф дає місячний ліміт, усе, що робить ваш застосунок, бере з нього, а резервна копія це велике завантаження в межах того самого тарифу. На безкоштовному тарифі нічна копія бази, близької до свого ліміту розміру, з'їдає ліміт усього місяця приблизно за десять днів. Це, а ще рядок підключення, до якого GitHub не може дістатися, і є дві речі між вами та робочою резервною копією, і жодної з них немає на сторінці Supabase.
Чи можна безкоштовно робити резервну копію Supabase через GitHub Action?
Так, і Supabase описує, як саме. Його сторінка про автоматичні резервні копії через GitHub Actions дає робочий процес, який встановлює CLI Supabase, вивантажує ваші ролі, схему й дані у три файли і за розкладом комітить їх назад у репозиторій.
GitHub за це теж нічого не бере, у певних межах. Приватний репозиторій на GitHub Free отримує 2 000 хвилин Actions на місяць, і нічний дамп невеликої бази витрачає з них невелику частину.
Ви отримуєте копію, яка щоночі залишає Supabase і опиняється на серверах іншої компанії. Саме цього власні резервні копії Supabase дати не можуть, бо щоденні копії на платному тарифі лишаються всередині облікового запису, який захищають.
Це правильна відповідь, коли справджуються три речі. Ваш застосунок уже живе в репозиторії GitHub, а так і є, якщо ви ввімкнули синхронізацію з GitHub у Lovable чи подібному конструкторі. Редагувати файл YAML вас не лякає. І ваша база даних невелика порівняно з лімітом egress вашого тарифу. Перші дві речі ви вже знаєте. Третя це арифметика, і їй присвячено окремий розділ нижче.
Робочий процес, розклад і секрет
Збережіть це як .github/workflows/supabase-backup.yml у приватному
репозиторії, де більше нічого немає:
name: supabase-backup
on:
schedule:
- cron: '17 3 * * *' # щодня о 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
Це робочий процес Supabase з трьома змінами.
- Він запускається за розкладом і коли ви натискаєте кнопку, і ніколи
інакше. Версія Supabase запускається ще й на кожен push і кожен pull
request у
main. Кожен запуск це повне завантаження вашої бази, тож у репозиторії, де лежить і ваш застосунок, кожна зміна, яку ви випускаєте, коштувала б ліміту однієї резервної копії. - Він запускається о 03:17, не на початку години. GitHub пише, що
заплановані запуски
можуть затримуватися на початку кожної години
і що за достатнього навантаження деякі завдання з черги відкидаються. Приклад
Supabase використовує
0 0 * * *, тобто саме початок години. Час указано в UTC, якщо ви не додастеtimezone, і підійде будь-яка хвилина, крім0. - Рядок із даними відповідає чинному
посібнику Supabase з резервного копіювання та відновлення,
який додає два виключення
-x, яких немає на сторінці робочого процесу. Тоді файли, які ви зберігаєте, саме ті, під які написано інструкції Supabase з відновлення.
Далі секрет. У репозиторії відкрийте Settings, потім Secrets and variables,
потім Actions, і додайте секрет репозиторію з назвою SUPABASE_DB_URL. Те, що
в нього покласти, вирішує, чи все це взагалі працюватиме, тому йому відведено
весь наступний розділ.
Щойно його збережено, запустіть робочий процес вручну з вкладки Actions, щоб
перший запуск відбувся у вас на очах. workflow_dispatch це рядок, який дає вам
кнопку Run workflow.
Який рядок підключення класти в секрет?
Рядок Session pooler, з кнопки Connect угорі вашого проєкту Supabase. Використовуйте саме його, навіть якщо сторінка робочого процесу Supabase показує у своєму прикладі пряме підключення.
Причина в IPv6, новішому типі інтернет-адреси. Пряме підключення Supabase, хост,
що починається з db. і закінчується на supabase.co,
за замовчуванням використовує IPv6,
а та сама сторінка зараховує GitHub Actions до сервісів, які приймають лише
IPv4. Раннер отримує адресу, до якої в нього немає маршруту, і перший дамп
падає, ще не записавши жодного байта. Помилка каже, що ім'я хоста не вдалося
перетворити на адресу або що мережа недосяжна, часто з довгою адресою, повною
двокрапок, поруч. Ця адреса і є IPv6. Виглядає це як неправильний пароль або
база, що впала, а справжня причина в мережі, до якої раннер не належить.
Посібник Supabase з резервного копіювання та відновлення радить за замовчуванням
брати рядок Session pooler, а прямий лише тоді, коли ваша мережа підтримує IPv6
або ви платите за додаток IPv4. Рядок pooler видно з трьох ознак: ім'я
користувача це postgres. і далі ідентифікатор вашого проєкту, хост
закінчується на pooler.supabase.com, а порт 5432.
Пароль у ньому це пароль вашої бази даних, заданий під час створення проєкту, а не ключ API. Якщо його ніхто не записав, Supabase дає скинути його в Database Settings; усе інше, що підключається зі старим паролем, перестане працювати, доки ви не дасте йому новий.
Цей рядок може читати й змінювати кожен рядок вашої бази даних. Він живе в секреті й більше ніде: ніколи у файлі робочого процесу, ніколи в повідомленні коміту і ніколи не вставлений в ІІ-асистента разом із помилкою, про яку ви хотіли спитати.
Чи рахується резервна копія Supabase в мій egress?
Так, уся. Supabase рахує як egress, тобто вихідний трафік, дані, які будь-який його сервіс надсилає назовні, а дамп через pooler записується як Shared Pooler Egress у той самий ліміт, що й ваш трафік API, завантаження файлів і все інше, що віддає ваш застосунок.
Повернімося до мобільного тарифу. Ліміт обнуляється щомісяця з новим розрахунковим періодом, кожен сервіс, яким користується ваш застосунок, бере з нього, а резервна копія це ще одне велике завантаження. А ще це сімейний тариф: Supabase застосовує ліміт до усієї вашої організації, тож усі проєкти в ній ділять один ліміт.
Станом на 26 вересня 2026 року сторінка цін Supabase давала безкоштовному тарифу 5 ГБ egress на місяць і ліміт 500 МБ на базу даних кожного проєкту, а тарифу Pro 250 ГБ egress. На безкоштовному тарифі є ще 5 ГБ cached egress, що покривають файли, які віддає CDN Supabase, і резервна копія їх ніколи не торкається. Інші ліміти безкоштовного тарифу і що робить кожен із них, коли ви його перетинаєте, описано в окремій статті.
Перевищення ліміту на безкоштовному тарифі не приносить рахунку. Supabase повідомляє вас і дає пільговий період, а якщо ви й далі перевищуєте, обмежує кожен проєкт організації. За словами Supabase, це може означати запити до API, на які відповідають помилкою 402, базу даних у режимі лише для читання або призупинені проєкти. Для застосунку з клієнтами це збій, який запустила ваша резервна копія.
Це стосується будь-якої резервної копії, що виходить із Supabase, включно з тією, яку продаємо ми. Копію, що зберігається поза обліковим записом, треба завантажити, щоб вона туди потрапила.
Як часто має запускатися резервне копіювання?
Так часто, як може оплатити ваш ліміт, і все зводиться до одного множення: розмір вашої бази даних на кількість запусків за місяць.
Supabase показує розмір вашої бази у звіті Database, у розділі Observability вашого проєкту. Дамп не має точно такого розміру. Він не містить індексів, які відбудовуються з їхніх визначень, і записує кожне значення як текст. Для планування цього досить, і це число, яке можна подивитися.
| Розмір бази | Щодня (30 запусків) | Двічі на тиждень | Щотижня |
|---|---|---|---|
| 50 МБ | 1,5 ГБ | 0,4 ГБ | 0,2 ГБ |
| 100 МБ | 3 ГБ | 0,9 ГБ | 0,4 ГБ |
| 250 МБ | 7,5 ГБ | 2,2 ГБ | 1,1 ГБ |
| 500 МБ | 15 ГБ | 4,3 ГБ | 2,2 ГБ |
У місяці приблизно 4,3 тижня, звідси й два останні стовпці.
На безкоштовному тарифі дві нижні щоденні цифри витрачають більше, ніж ліміт усього місяця, ще до того, як ваш застосунок відповів на бодай один запит. Щоденний розклад вичерпує всі 5 ГБ десь на 160 МБ, а трафік вашого застосунку береться з того самого ліміту, тож перш ніж обирати, подивіться egress за минулий місяць на сторінці Usage вашої організації. Щотижневий розклад вміщається за будь-якого розміру, який дозволяє безкоштовний тариф.
Рядок cron єдине, що ви змінюєте:
17 3 * * * щодня о 03:17 UTC
17 3 * * 1,4 щопонеділка й щочетверга
17 3 * * 0 щонеділі
На Pro ця арифметика майже перестає мати значення. 250 ГБ на місяць оплачують щоденний дамп одного проєкту приблизно до 8 ГБ, а це саме той дисковий простір, який тариф Pro включає на проєкт.
Чому мій робочий процес падає з помилкою версії pg_dump?
Бо pg_dump, який він запустив, старіший за вашу базу даних. Таке трапляється
лише з робочим процесом, що викликає pg_dump напряму, а не через CLI Supabase.
CLI має власний pg_dump, і це одна з причин, чому робочий процес вище
використовує саме її.
Раннер ubuntu-latest від GitHub постачається з
встановленим PostgreSQL 16.
Проєкти Supabase можуть працювати на Postgres 17, а Postgres документує, що
pg_dumpне вивантажує з сервера новішої мажорної версії, ніж його власна,
і воліє відмовитися, ніж ризикувати зіпсованим файлом. Запуск зупиняється з:
pg_dump: error: aborting because of server version mismatch
pg_dump: detail: server version: 17.…; pg_dump version: 16.…
Версія вашого проєкту вказана в Project Settings, потім General, якщо хочете перевірити. З вашими обліковими даними все гаразд.
Виправлення складається з двох кроків. Додайте власний репозиторій пакетів PostgreSQL, щоб раннер міг встановити клієнт версії 17, а тоді викликайте цей клієнт за повним шляхом:
- 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" …
Аргументи, які у вас уже були, лишіть після рядка підключення. Повний шлях
важливий: в Ubuntu звичайний pg_dump це обгортка, яка обирає версію за вас, а
на раннері вже є встановлений PostgreSQL 16, який вона може обрати.
Власний pg_dump CLI теж має версію, і береться вона не з вашого проєкту. Без
supabase/config.toml поруч із робочим процесом, як у репозиторії, де більше
нічого немає, CLI
бере pg_dump 17.
На проєкті, що досі працює на Postgres 15, data.sql тоді містить
SET transaction_timeout = 0, параметр, якого Postgres 15 не має, і відтворення
зупиняється на цьому рядку в будь-якій базі Postgres 15, зокрема в проєкті, з
якого взято файл. Ми перевірили це 4 жовтня 2026 року з pg_dump 17.11 і
Postgres 15.19. Якщо ваш проєкт на 15, додайте один рядок у блок env завдання,
щоб дамп робився відповідним pg_dump:
env:
SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
SUPABASE_DB_MAJOR_VERSION: '15'
Сама резервна копія в будь-якому разі завершується без помилки, тож без цього рядка розбіжність видно лише в день, коли файл відтворюють.
Чи можна комітити резервну копію в мій репозиторій?
У приватний можна. Саме це робить робочий процес Supabase, і його сторінка двічі каже ніколи не робити резервну копію своїх даних у публічний репозиторій.
Три речі про репозиторій як дім для бази даних, і жодної з них на тій сторінці немає:
- Кожен, хто може читати репозиторій, може читати ваших користувачів.
data.sqlмістить кожен рядок: адреси пошти, імена, усе, що зберігають ваші таблиці, і ваші облікові записи теж. Тому робочий процес отримує окремий репозиторій, де більше нікого немає, а не той, де лежить код вашого застосунку, який ваш конструктор, можливо, синхронізує і куди одного дня може бути запрошено фрилансера. - Копія кожної ночі лишається в історії. Git зберігає кожну версію кожного файлу. Коли хтось із ваших клієнтів просить видалити свій обліковий запис, його рядок і далі лежить у кожному попередньому коміті цього репозиторію, доки ви не перепишете його історію.
- На 100 МіБ усе зупиняється. GitHub попереджає про файли понад 50 МіБ і
блокує файли понад 100 МіБ,
а та сама сторінка каже, що Git не створено для роботи з великими SQL-файлами.
Тієї ночі, коли
data.sqlперетне цю межу, крок коміту впаде, і кожен запуск після нього падатиме так само.
Коли ви наблизитеся до межі, той самий робочий процес може вивантажувати три файли у ваш власний бакет сховища замість коміту. Або це та точка, де робити це самому вже не дешево, і саме з неї починається останній розділ.
Чого робочий процес не копіює?
Ваших завантажених файлів. Supabase Storage тримає кожен файл поза базою даних, а рядок з його описом усередині, тож дамп несе рядок, а не картинку. Резервна копія Storage це окрема робота, хоч яким шляхом ви йдете.
Ваші користувачі в ньому є. Дамп даних проходить схему auth, де живуть ваші
облікові записи, і пошук auth.users у data.sql їх показує. Проєкту довкола
бази даних у ньому немає: Edge Functions, налаштування провайдерів входу, ключі
API і секрети це конфігурація, а не дані, і
список того, чого немає в жодній із двох копій,
варто прочитати, перш ніж він знадобиться.
Чи означає зелений запуск, що резервна копія вдалася?
Він означає, що завдання завершилося без помилки, а це менше твердження. Три дуже різні результати закінчуються тією самою зеленою галочкою:
- Хороший дамп правильного проєкту.
- Дамп не того проєкту, бо в секреті лежить рядок staging-копії.
- Дамп без жодного рядка, бо хтось відредагував рядок із даними і
--data-onlyзник, що знову перетворює його на дамп схеми.
Один результат не лишає жодного сліду. Запланований запуск, який GitHub відкидає під навантаженням, не з'являється як збій, бо немає запуску, який міг би впасти. А коли запуск таки падає, GitHub надсилає сповіщення людині, яка останньою редагувала рядок cron. Якщо це налаштовував для вас фрилансер, лист про вашу резервну копію піде до його пошти.
Розрізнити їх може лише відновлення. Відтворіть три файли в тимчасовому проєкті Supabase або локальному, порівняйте кількість рядків у важливих для вас таблицях зі справжніми і увійдіть як справжній користувач. Як відновити резервну копію Supabase містить команди в тому порядку, який дає Supabase. Зробіть це один раз зараз і знову щоразу, коли змінюєте робочий процес.
Відновлення й підрахунок можна доручити кожному запуску. Ми опублікували версію цього робочого процесу як GitHub Action з відкритим кодом, Reeve-page/supabase-backup-action. Вона робить ті самі три дампи, зберігає копію як артефакт робочого процесу або в бакеті S3 чи R2, потім відтворює її в тимчасовій базі даних Supabase на раннері й порівнює кількість рядків кожної таблиці з файлом. Запуск стає зеленим лише тоді, коли кожна таблиця повернулася з тими рядками, що є у файлі:
- uses: Reeve-page/supabase-backup-action@v1
with:
db-url: ${{ secrets.SUPABASE_DB_URL }}
Вона безкоштовна й поширюється за ліцензією MIT. Кожен запуск і далі завантажує всю базу, тож розрахунок egress вище стосується її без змін, а вхід вам і далі треба перевірити самостійно.
Коли варто перестати робити це самостійно?
Коли арифметика перестає сходитися, коли файл переростає репозиторій або коли ніхто не відновлює копії. Досить будь-чого одного.
- Ваша база переросла безкоштовний ліміт. Pro піднімає egress до 250 ГБ і додає власні щоденні резервні копії Supabase, що зберігаються сім днів і відновлюються однією кнопкою. Лишіть робочий процес працювати поруч із ними, бо ці копії живуть усередині облікового запису.
data.sqlнаближається до 100 МіБ. Перенести його в бакет означає більше YAML і більше того, що хтось має підтримувати в робочому стані.- Ніхто жодного разу не відновлював копію. Робочий процес і далі писатиме файли, відкриваються вони чи ні.
- Ваші користувачі завантажують файли. Робочий процес до них не дістає, а завдання, яке дістає, це ще одне, яке треба тримати живим.
Де тут Reeve Care
Care робить копію за розкладом, зберігає її поза вашим обліковим записом Supabase і перевіряє кожну копію, перш ніж її зарахувати.
- Щодня на Care і частіше на тарифах вище, без нічого у вашому репозиторії і без рядка підключення в CI.
- Перечитана й перелічена. Кожну копію відкривають і звіряють із тим, що в неї потрапило, а дата на вашій панелі це остання копія, яка пройшла цю перевірку, а ніколи не останній запуск.
- Ваші облікові записи в ній є, а завантажені файли теж потрапляють туди, щойно ви підключите доступ до Storage.
- Відновлення це одна кнопка, і перш ніж щось замінити, робиться копія поточного стану.
- Будь-яку копію можна завантажити як zip із
schema.sql,data.sqlіroles.sql, тими самими трьома файлами, що робить цей робочий процес, плюс кількість рядків у кожній таблиці.
Care зберігає копію вашої бази даних Supabase. Копію, перевірку й відновлення покроково намальовано на сторінці про резервні копії Supabase, а що входить у кожен тариф, написано на сторінці цін.
Що зробити цього тижня
Що робити
- Подивіться розмір вашої бази даних і egress за минулий місяць і виберіть розклад за таблицею, перш ніж писати рядок cron.
- Створіть приватний репозиторій, у якому немає нічого, крім робочого процесу, і покладіть рядок Session pooler у його секрет
SUPABASE_DB_URL. - Якщо ви почали з прикладу Supabase, видаліть тригери push і pull request і зсуньте cron з початку години.
- Запустіть його один раз вручну з вкладки Actions, а тоді пошукайте в
data.sqlauth.usersі таблицю, у якій точно є рядки. - Відновіть одну копію в тимчасовому проєкті й увійдіть як справжній користувач.
- Копіюйте файли Storage окремим завданням.
Перш ніж закрити цю вкладку, відкрийте в Supabase сторінку Usage вашої організації і подивіться egress за минулий місяць. Це число поруч із розміром вашої бази даних і обирає ваш рядок cron. А якщо ви ще порівнюєте безкоштовний шлях із платними, чотири види інструментів резервного копіювання Supabase зіставлено поруч.
Поширені запитання
Чи можна робити резервну копію Supabase безкоштовно?
Так. Supabase описує робочий процес GitHub Actions, який встановлює його CLI, за розкладом вивантажує ваші ролі, схему й дані у три файли і комітить їх у репозиторій. Приватний репозиторій на GitHub Free отримує 2 000 хвилин Actions на місяць, і нічний дамп невеликої бази витрачає з них невелику частину. Що резервна копія справді витрачає, так це ваш ліміт egress у Supabase, бо кожен запуск завантажує всю базу.
Як часто має запускатися cron резервної копії Supabase?
Так часто, як може оплатити ваш ліміт egress. Помножте розмір бази на кількість запусків за місяць: база на 100 МБ при щоденному дампі переносить близько 3 ГБ, а на 500 МБ близько 15 ГБ. Безкоштовний тариф дає 5 ГБ на всю організацію, спільні з трафіком вашого застосунку. Ставте завдання на хвилину, що не припадає на початок години, бо GitHub пише, що заплановані запуски на початку кожної години можуть затримуватися, а деякі відкидатися.
Чи рахується резервна копія Supabase в мій egress?
Так. Supabase рахує як egress дані, які будь-який його сервіс надсилає назовні, а дамп через connection pooler записується як Shared Pooler Egress у той самий ліміт, що й ваш трафік API. Ліміт належить усій організації, а не одному проєкту. На безкоштовному тарифі повторне перевищення закінчується обмеженнями для кожного проєкту організації.
Чому мій робочий процес резервного копіювання падає на першому запуску?
Для цього налаштування характерні два збої. Перший це рядок підключення: пряме підключення Supabase використовує IPv6, якщо ви не платите за додаток IPv4, а Supabase зараховує GitHub Actions до сервісів, які дістаються лише до IPv4, тож беріть рядок Session pooler. Другий стосується робочих процесів, що викликають pg_dump напряму: клієнт PostgreSQL 16 на раннері відмовляється вивантажувати проєкт на Postgres 17 і зупиняється з aborting because of server version mismatch.
Чи можна комітити резервну копію Supabase у мій репозиторій GitHub?
У приватний можна, саме це робить робочий процес Supabase. У публічний ніколи, і Supabase каже це двічі на одній сторінці. Дайте йому окремий репозиторій, який ніхто інший не може читати, бо файл із даними містить адреси електронної пошти ваших користувачів. І розраховуйте переїхати, коли база виросте: GitHub попереджає про файли понад 50 МіБ і відхиляє файли понад 100 МіБ.
Як дізнатися, чи файл резервної копії придатний?
Відновіть його. Зелений запуск означає, що завдання завершилося без помилки, а дамп не того проєкту чи дамп без жодного рядка завершуються так само. Відтворіть три файли в тимчасовому проєкті Supabase або локальному, порівняйте кількість рядків у таблицях, які для вас важливі, і увійдіть як справжній користувач. Саме ця перевірка каже, чи файл відкривається.