Как перенести проект с Supabase Cloud на сервер в России

27 сентября 2026 · 13 мин чтения · Данные и 152-ФЗ, пункты 13, 17, 20

Коротко: проект переезжает с Supabase Cloud на свой сервер за семь шагов: установить self-hosted Supabase из официальной Docker-конфигурации, настроить домен и HTTPS, перенести базу командами supabase db dump и psql, перенастроить авторизацию, скопировать файлы Storage через S3-протокол, перенести Edge Functions с секретами и переключить фронтенд на новый URL и ключи. Учётные записи пользователей переезжают вместе с базой, но войти заново придётся всем: ключи подписи токенов на новом сервере другие. Облачный проект не удаляйте, пока не закончится период, в течение которого вы готовы откатиться.

Команды ниже взяты из документации Supabase на сентябрь 2026 года, актуальная версия конфигурации на эту дату self-hosted/v0.8.2. Конфигурация обновляется примерно раз в месяц, поэтому перед переносом сверьтесь с документацией. Почему проекты с персональными данными россиян приходится переносить, разобрано в статье «Supabase в России в 2026 году».

Что подготовить перед переносом Supabase

  • виртуальную машину с Linux в российском дата-центре: Yandex Cloud, Selectel, Timeweb Cloud или другого провайдера;
  • домен и доступ к его DNS-записям;
  • на рабочем компьютере: Supabase CLI, Docker (CLI запускает pg_dump в контейнере), psql и rclone;
  • пароль базы облачного проекта, список Edge Functions и значения их секретов;
  • настройки OAuth-провайдеров и SMTP, если вы ими пользуетесь.

Какой сервер нужен для self-hosted Supabase

Официальные требования из раздела System requirements:

Ресурс Минимум Рекомендуется
RAM 4 ГБ от 8 ГБ
CPU 2 ядра от 4 ядер
Диск 40 ГБ SSD от 80 ГБ SSD

Наша оценка, не норматив: для рабочего проекта начинайте с рекомендуемой конфигурации и смотрите на загрузку после переезда. Диск считайте так: размер базы, плюс объём файлов Storage, плюс запас под дампы и обновления. Если вам не нужны Realtime, Storage, imgproxy или Edge Functions, их можно убрать из docker-compose.yml, и требования снизятся. Логи и аналитика (Logflare и Vector) по умолчанию выключены, их включение добавит нагрузку.

У Timeweb Cloud есть своя инструкция по установке Supabase, последний раз её обновляли в феврале 2025 года. Официальная конфигурация с тех пор менялась, поэтому сверяйте команды с документацией Supabase.

Шаг 1. Установите Supabase на сервер

Для Debian, Ubuntu, RHEL, CentOS и Fedora есть скрипт быстрой установки. Он ставит Docker, скачивает конфигурацию, спрашивает адреса и генерирует все секреты:

curl -fsSL https://supabase.link/setup.sh | sh

Ссылка ведёт на setup.sh в репозитории Supabase, перед запуском его можно прочитать. После скрипта:

cd supabase-project && \
sh run.sh start

Ручная установка работает на любой ОС:

git clone --depth 1 --branch self-hosted/v0.8.2 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/. supabase-project
cd supabase-project && cp .env.example .env
printf 'ref=self-hosted/v0.8.2\n' > .supabase-version
docker compose pull
sh utils/generate-keys.sh
sh utils/add-new-auth-keys.sh

Не запускайте Supabase с паролями и ключами из .env.example. После скриптов проверьте в .env:

  • POSTGRES_PASSWORD — пароль базы;
  • DASHBOARD_USERNAME и DASHBOARD_PASSWORD — вход в Studio, пароль должен содержать хотя бы одну букву;
  • SUPABASE_PUBLIC_URL, API_EXTERNAL_URL, SITE_URL — адреса, их вы заполните на шаге 2.

Запустите стек и проверьте состояние сервисов. Через минуту все должны быть в статусе Up (healthy):

sh run.sh start
docker compose ps

Сравните версии PostgreSQL. В self-hosted конфигурации по умолчанию стоит Postgres 17, начиная с версии 0.6.0 (CHANGELOG), хотя гайд по восстановлению ещё упоминает Postgres 15. Облачный проект может работать на любой из них.

Шаг 2. Настройте домен и HTTPS

  1. Создайте DNS-запись типа A, например api.example.ru, с IP сервера. Заранее снизьте TTL (время кэширования записи), чтобы переключение и откат применялись быстрее.
  2. Укажите адреса в .env по гайду Configure HTTPS. SITE_URL — адрес вашего фронтенда, куда пользователь попадает после входа.
SUPABASE_PUBLIC_URL=https://<your-domain>
API_EXTERNAL_URL=https://<your-domain>/auth/v1
SITE_URL=https://<your-app-domain>
  1. Включите обратный прокси с сертификатом Let's Encrypt. Проще всего через Caddy:
sh run.sh config add caddy
sh run.sh start

Для Nginx задайте в .env переменные PROXY_DOMAIN и CERTBOT_EMAIL и выполните sh run.sh config add nginx. Порты 80 и 443 должны быть открыты.

  1. Проверьте HTTPS. Ответ 401 означает, что Auth доступен:
curl -I https://<your-domain>/auth/v1/

Закройте лишние порты. Пулер соединений Supavisor по умолчанию публикует на хосте порты 5432 и 6543 (Accessing Postgres), а Studio защищена только HTTP Basic Auth. Снаружи оставьте 80, 443 и SSH, остальное закройте файрволом.

Настройте почту. В .env.example указан тестовый почтовый сервер (SMTP_HOST=supabase-mail). Пропишите рабочий SMTP в переменных SMTP_*, иначе письма подтверждения и сброса пароля не дойдут до пользователей.

Шаг 3. Перенесите базу данных

Основной гайд: Restore a Platform Project to Self-Hosted.

  1. В облачном проекте нажмите Connect и скопируйте строку подключения. По умолчанию используйте Session pooler; прямое подключение требует IPv6 или подключённого IPv4 add-on (Backup and restore).
  2. Проверьте расширения запросом select * from pg_extension; в облачной базе. Нестандартные включите на новом сервере до восстановления.
  3. Выгрузите роли, схему и данные:
supabase db dump --db-url "[CONNECTION_STRING]" -f roles.sql --role-only
supabase db dump --db-url "[CONNECTION_STRING]" -f schema.sql
supabase db dump --db-url "[CONNECTION_STRING]" -f data.sql --use-copy --data-only

Используйте CLI вместо прямого pg_dump. CLI исключает внутренние схемы Supabase и служебные роли, а дамп через pg_dump захватит их и при восстановлении упадёт с ошибками прав.

  1. Загрузите дамп на новый сервер. your-tenant-id — значение POOLER_TENANT_ID из .env:
psql \
  --single-transaction \
  --variable ON_ERROR_STOP=1 \
  --file roles.sql \
  --file schema.sql \
  --command 'SET session_replication_role = replica' \
  --file data.sql \
  --dbname "postgres://postgres.your-tenant-id:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres"

Параметр session_replication_role = replica отключает триггеры на время загрузки, чтобы данные не шифровались повторно.

  1. Проверьте результат:
\dt public.*
SELECT count(*) FROM auth.users;
SELECT * FROM pg_extension;

Частые проблемы из раздела Troubleshooting:

  • В облаке версии Auth и Storage новее, и data.sql может содержать COPY в таблицы и колонки, которых на сервере нет. Закомментируйте строку COPY ... FROM stdin; и соответствующий ей терминатор \.. Сначала запустите загрузку без --single-transaction, чтобы увидеть все ошибки, затем выполните финальную загрузку с этим флагом.
  • Если на сервере Postgres 15, а в облаке 17, отключите несовместимую настройку: sed -i 's/^SET transaction_timeout/-- &/' data.sql.
  • Пароли собственных ролей с атрибутом LOGIN в дамп не попадают. Задайте их вручную: ALTER ROLE your_custom_role WITH PASSWORD 'new-password';.

Ещё три пункта, которые легко пропустить:

  • Изменения в схемах auth и storage, например свои триггеры или политики RLS на storage.objects, CLI в дамп схемы не включает. Их переносят отдельно по разделу Schema changes to auth and storage. Для движка сравнения migra команда такая, для pg-delta флаг --schema не нужен:
supabase link --project-ref "$OLD_PROJECT_REF"
supabase db diff --linked --schema auth,storage > changes.sql
  • Supabase Vault и pgsodium. Дамп содержит только зашифрованные данные, корневого ключа в нём нет. Supabase предупреждает, что API отдаёт ключ только для активного проекта, после паузы или удаления его не получить. Как установить этот ключ в self-hosted, официальный гайд не описывает, поэтому такой перенос обязательно проверьте на тестовом сервере.
  • Realtime. Если приложение подписывается на изменения таблиц, проверьте публикации для нужных таблиц на новом сервере.

В версии 0.8.2 из инициализации базы убран параметр app.settings.jwt_secret (CHANGELOG). Если ваши SQL-функции его читают, их придётся переписать.

Шаг 4. Перенесите пользователей и настройки авторизации

Таблица auth.users входит в дамп, поэтому учётные записи сохраняются. Что меняется (Auth considerations):

  • Все пользователи выйдут из системы. Ключи подписи JWT (JSON Web Token, токен сессии пользователя) на новом сервере другие, и токены из облака перестанут работать. Предупредите пользователей заранее.
  • JWT-секрет и API-ключи гайд велит сгенерировать заново, это уже сделали скрипты на шаге 1.
  • Вход через Google, GitHub, Apple и другие OAuth-провайдеры настраивается переменными GOTRUE_EXTERNAL_* в .env (Configure social login). В консолях провайдеров замените redirect URL с *.supabase.co на новый домен.
  • Разрешённые адреса для редиректа после входа задаются в SITE_URL и ADDITIONAL_REDIRECT_URLS.
  • Свои шаблоны писем переносятся по гайду Custom email templates.

Шаг 5. Перенесите файлы из Storage

Гайд: Copy Storage Objects from Platform.

Копировать файлы напрямую в volumes/storage/ нельзя: у self-hosted Storage своя внутренняя структура. Переносите через S3-протокол, тогда Storage создаст правильные записи метаданных.

  1. На сервере проверьте в .env переменные REGION, S3_PROTOCOL_ACCESS_KEY_ID и S3_PROTOCOL_ACCESS_KEY_SECRET и то, что они переданы сервису storage (Enable the S3 protocol endpoint).
  2. В облачном проекте откройте Storage → S3 Configuration → Access keys и создайте пару ключей.
  3. Бакеты уже есть на сервере, их определения пришли вместе с базой на шаге 3.
  4. Опишите оба хранилища в ~/.config/rclone/rclone.conf. После настройки HTTPS адрес сервера будет https://<your-domain>/storage/v1/s3:
[platform]
type = s3
provider = Other
access_key_id = your-platform-access-key-id
secret_access_key = your-platform-secret-access-key
endpoint = https://your-project-ref.supabase.co/storage/v1/s3
region = your-project-region

[self-hosted]
type = s3
provider = Other
access_key_id = your-self-hosted-access-key-id
secret_access_key = your-self-hosted-secret-access-key
endpoint = http://your-domain:8000/storage/v1/s3
region = your-self-hosted-region
  1. Проверьте подключение, скопируйте все бакеты и сверьте объём:
rclone lsd platform:
rclone lsd self-hosted:

for bucket in $(rclone lsf platform: | tr -d '/'); do
  echo "Copying bucket: $bucket"
  rclone copy "platform:$bucket" "self-hosted:$bucket" --progress
done

rclone size platform:your-storage-bucket && \
rclone size self-hosted:your-storage-bucket

API облачного проекта работает через Cloudflare, а трафик к Cloudflare из России ограничивается (подробнее в первой статье). Крупные файлы при копировании на российский сервер могут обрываться, поэтому сначала проверьте копирование одного бакета. Повторный rclone copy пропускает файлы, которые уже совпадают на обеих сторонах, так что прерванное копирование можно продолжить.

Файлы можно сразу хранить в российском S3-хранилище: Storage поддерживает S3-совместимые бэкенды через STORAGE_BACKEND: s3, GLOBAL_S3_ENDPOINT и другие переменные (S3-compatible providers). Российские хранилища в документации Supabase не упоминаются, совместимость проверьте на тестовом бакете.

Если в таблицах хранятся полные адреса файлов вида https://<ref>.supabase.co/storage/v1/object/public/..., замените в них домен, например функцией replace(). Сначала сделайте это на копии базы.

Шаг 6. Перенесите Edge Functions и секреты

Гайд: Self-hosted Functions.

  1. Скачайте код функции кнопкой Download в дашборде или через CLI:
supabase functions download <function-name> --project-ref <ref>
  1. Положите каждую функцию в volumes/functions/<function-name>/index.ts и перезапустите сервис:
sh run.sh restart functions
  1. Секреты перенесите в файл .env.functions, подключите его через env_file у сервиса functions в docker-compose.yml и пересоздайте контейнер. Значения секретов возьмите из своих источников: кабинетов сторонних сервисов, менеджера паролей.
sh run.sh recreate functions

Что учесть:

  • внутри функций SUPABASE_URL указывает на внутренний адрес http://api-gw:8000, для ссылок, которые увидит пользователь, используйте SUPABASE_PUBLIC_URL;
  • по умолчанию на вызов отводится 150 МБ памяти и 60 секунд, лимиты задаются в volumes/functions/main/index.ts;
  • проверку JWT включает и выключает переменная FUNCTIONS_VERIFY_JWT в .env;
  • в облаке функции развёрнуты в нескольких регионах, на своём сервере они работают на одной машине;
  • если функции обращаются к зарубежным API, проверьте их доступность с российского сервера до переключения;
  • вебхуки внешних сервисов, например платёжных систем или ботов, которые вызывают <ref>.supabase.co/functions/v1/..., перенастройте на новый домен.

Шаг 7. Переключите фронтенд на новый сервер

Клиент supabase-js создаётся с двумя параметрами: URL проекта и ключ. Новые значения выводит команда sh run.sh secrets:

Было в облаке Стало на своём сервере
https://<ref>.supabase.co SUPABASE_PUBLIC_URL, например https://api.example.ru
anon key или publishable key SUPABASE_PUBLISHABLE_KEY или ANON_KEY
service_role key или secret key SUPABASE_SECRET_KEY или SERVICE_ROLE_KEY, только на сервере

Self-hosted Supabase одновременно принимает новые ключи (sb_publishable_..., sb_secret_...) и старые JWT-ключи ANON_KEY и SERVICE_ROLE_KEY (New API Keys). В отличие от облака, ключей каждого типа здесь по одному.

Секретный ключ не должен попасть в код фронтенда.

Где искать старые адреса и ключи:

  • .env фронтенда: VITE_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_URL и похожие переменные;
  • файлы, где клиент создаётся с константами прямо в коде, так часто бывает в проектах из конструкторов;
  • переменные окружения в CI/CD и на хостинге фронтенда;
  • мобильные приложения: старые версии будут ходить в облако, пока пользователи не обновятся.

После переключения проверьте вход, регистрацию с письмом, чтение и запись под обычным пользователем, загрузку файла, вызов функции и подписку Realtime. Как убедиться, что политики доступа переехали и работают, описано в статье «RLS в Supabase».

Как спланировать простой и откат

Начните с пробного переноса: разверните сервер, перенесите базу и файлы, прогоните проверки. Замерьте время каждого шага. Так вы получите оценку простоя для своего объёма данных.

Порядок переключения:

  1. За сутки снизьте TTL DNS-записей.
  2. Остановите запись в облачный проект, например включите в приложении режим обслуживания. Дамп фиксирует состояние на момент выгрузки, всё записанное после него потеряется.
  3. Сделайте финальный дамп и восстановление, докопируйте файлы повторным rclone copy.
  4. Выложите фронтенд с новым URL и ключами, обновите вебхуки и redirect URL у OAuth-провайдеров.
  5. Прогоните проверки и следите за логами командой sh run.sh logs.

План отката:

  • облачный проект остаётся нетронутым, откат означает выкладку фронтенда со старыми URL и ключами;
  • данные, записанные на новом сервере после переключения, при откате придётся переносить обратно той же процедурой, поэтому заранее назначьте срок, после которого откат не делаете;
  • при неоплаченном счёте Supabase ставит проекты на паузу (Billing FAQ), учитывайте это, пока облачный проект нужен как запасной вариант;
  • корневой ключ Vault заберите до паузы или удаления облачного проекта.

Чем self-hosted Supabase отличается от Supabase Cloud

По документации Supabase:

  • недоступны ветки (branching), расширенные метрики, управляемые бэкапы и PITR, аналитические и векторные бакеты, ETL и Management API;
  • одна установка обслуживает один проект, настройки задаются переменными окружения;
  • логи и аналитика по умолчанию выключены;
  • ключи ротируются скриптом sh utils/rotate-new-api-keys.sh --update-env, в дашборде этого нет;
  • поддержку оказывает только сообщество;
  • обновления (скрипт update.sh), безопасность, резервные копии, мониторинг и отказоустойчивость на вас.

Резервное копирование настройте с первого дня. CLI снимает дамп и с self-hosted базы через флаг --db-url (supabase db dump), файлы Storage копируйте отдельно. Храните копии на другом сервере, тоже в России. Секреты по умолчанию лежат в .env, для продакшена Supabase рекомендует менеджер секретов.

Если хотите, чтобы проект проверил инженер, оставьте заявку на Кодосмотр.