Бэкапы при локальной разработке: SQLite, PostgreSQL, MySQL и Docker

Обновлено:

Бэкапы продакшена делают почти все, а локальную базу не бэкапит никто — до первой команды, которая стирает несколько часов ручной настройки тестовых данных. Надо ли бэкапить локальную базу? Да, и настраивается это за пару минут.

Почему локальные данные важнее, чем кажется

Продакшен обычно хоть как-то защищён: автоматические копии у облачного провайдера, мониторинг, алерты. Локальная среда остаётся без страховки.

Типичные сценарии потери данных на своей машине:

  • миграция с ошибкой стёрла данные в нескольких таблицах;
  • AI-агент (Cursor, Claude Code, Copilot) выполнил деструктивный SQL — подробнее в статье ИИ стёр базу данных: что делать;
  • docker compose down -v удалил тома вместе с данными;
  • случайный rm -rf в директории проекта;
  • переустановка ОС без сохранения данных Docker;
  • откат ветки в Git, хотя миграции уже применились к базе.

Что именно нужно бэкапить

  • Схема (таблицы, индексы, триггеры) обычно уже версионирована миграциями (Alembic, Flyway, Prisma Migrate, Django migrations) и живёт в Git.
  • Данные (строки в таблицах) — то, что действительно теряется.
  • Seed-данные — отдельная тема, о ней ниже.

Бэкап SQLite

SQLite — один файл на диске, но копировать его через cp во время записи небезопасно: в режиме WAL часть транзакций ещё лежит в файле -wal, и копия получится несогласованной. Используйте встроенную команду .backup или VACUUM INTO — подробности в статье как сделать бэкап SQLite без повреждения.

mkdir -p backups
sqlite3 my_database.db ".backup 'backups/backup_$(date +%Y%m%d_%H%M%S).db'"

Команда использует online backup API и даёт согласованную копию даже при открытых соединениях. В режиме WAL она не мешает приложению писать.

Восстановление

Остановите приложение, затем:

sqlite3 my_database.db ".restore 'backups/backup_20260501_143000.db'"

Если восстанавливаете простым копированием файла, удалите рядом с базой старые my_database.db-wal и my_database.db-shm, иначе SQLite попытается применить к восстановленному файлу чужой журнал.

npm-скрипты

{
  "scripts": {
    "db:backup": "mkdir -p backups && sqlite3 app.db \".backup 'backups/backup_$(date +%Y%m%d_%H%M%S).db'\"",
    "premigrate": "npm run db:backup"
  }
}

Хук premigrate автоматически делает бэкап перед npm run migrate. Подстановка $(date ...) работает в bash/zsh; на Windows запускайте скрипт из Git Bash или WSL либо используйте PowerShell:

New-Item -ItemType Directory -Force backups | Out-Null
$ts = Get-Date -Format 'yyyyMMdd_HHmmss'
sqlite3 app.db ".backup 'backups/backup_$ts.db'"

Makefile

DB_FILE=app.db
BACKUP_DIR=backups

db-backup:
	@mkdir -p $(BACKUP_DIR)
	sqlite3 $(DB_FILE) ".backup '$(BACKUP_DIR)/backup_$(shell date +%Y%m%d_%H%M%S).db'"
	@echo "Бэкап создан"

db-list:
	@ls -la $(BACKUP_DIR)

Бэкап PostgreSQL

Базовый дамп

pg_dump -h localhost -U postgres -Fc -f backups/backup_$(date +%Y%m%d_%H%M%S).dump mydb

-Fc (custom-формат) создаёт сжатый архив, из которого можно восстанавливать отдельные таблицы. Человекочитаемый SQL:

pg_dump -h localhost -U postgres --format=plain -f backup.sql mydb

Восстановление

# custom-формат: только pg_restore
createdb -h localhost -U postgres mydb_restored
pg_restore -h localhost -U postgres -d mydb_restored backup.dump

# plain SQL: только psql
psql -h localhost -U postgres -v ON_ERROR_STOP=1 -d mydb_restored -f backup.sql

Все варианты восстановления — в статье как восстановить базу из бэкапа.

Пароль через .pgpass

Создайте ~/.pgpass (Linux/macOS) или %APPDATA%\postgresql\pgpass.conf (Windows):

localhost:5432:mydb:postgres:yourpassword

На Linux/macOS выставьте права chmod 600 ~/.pgpass, иначе файл будет проигнорирован.

Только данные или отдельные таблицы

# только данные — схема уже в миграциях
pg_dump -h localhost -U postgres --data-only -Fc -f data_only.dump mydb

# выбранные таблицы
pg_dump -h localhost -U postgres -t users -t orders -Fc -f selected_tables.dump mydb

Бэкап MySQL и MariaDB

mysqldump -u root -p --single-transaction --routines --triggers mydb > backup_$(date +%Y%m%d_%H%M%S).sql

-p без значения спросит пароль интерактивно — не пишите пароль прямо в команде, он будет виден в списке процессов. --single-transaction даёт согласованный снимок без блокировки таблиц, но только для InnoDB.

mysql -u root -p mydb < backup.sql

Бэкап MongoDB

mongodump --uri="mongodb://localhost:27017/mydb" --out=backups/$(date +%Y%m%d_%H%M%S)
mongorestore --uri="mongodb://localhost:27017" --drop backups/20260501_143000/

Docker и базы данных: частые ошибки

Ошибка №1: не понимать, что удаляет down -v

# Данные в анонимном томе: после down и нового up база окажется пустой
services:
  db:
    image: postgres:16
# Bind mount: данные лежат в папке проекта и переживают даже down -v
services:
  db:
    image: postgres:16
    volumes:
      - ./pgdata:/var/lib/postgresql/data

Важно: docker compose down -v удаляет и именованные тома, объявленные в compose-файле. Bind mount (./pgdata) он не трогает. Добавьте pgdata/ в .gitignore.

Бэкап из контейнера

# PostgreSQL: без флага -t, иначе TTY может испортить переводы строк в дампе
docker exec my_postgres pg_dump -U postgres mydb > backup.sql

# SQLite: сначала согласованная копия внутри контейнера, потом docker cp
docker exec my_app sqlite3 /app/database.db ".backup '/tmp/backup.db'"
docker cp my_app:/tmp/backup.db ./backups/backup_$(date +%Y%m%d_%H%M%S).db

Если в образе приложения нет sqlite3, остановите контейнер перед docker cp файла базы. Больше примеров — в статье бэкап БД в Docker и Coolify.

Восстановление в Docker

docker exec -i my_postgres psql -U postgres -v ON_ERROR_STOP=1 mydb < backup.sql

Seed-данные vs бэкап

| Критерий | Бэкап | Seed-скрипты | |---|---|---| | Совместимость со схемой | Может конфликтовать после миграций | Обновляются вместе с миграциями | | Передача коллеге | Нужна та же версия схемы | Достаточно запустить скрипт | | Воспроизводимость | Снимок конкретного момента | Одинаковый результат при каждом запуске | | Размер | Растёт вместе с данными | Небольшой, хранится в коде | | Назначение | Вернуть конкретное состояние | Стандартный набор тестовых данных |

Seed-скрипты — для стандартного набора тестовых данных, бэкапы — чтобы вернуть конкретное состояние после аварии.

# management/commands/seed.py
from django.core.management.base import BaseCommand
from myapp.models import User, Product

class Command(BaseCommand):
    help = 'Seed database with test data'

    def handle(self, *args, **options):
        User.objects.get_or_create(email='admin@test.com', defaults={'name': 'Admin'})
        Product.objects.bulk_create([
            Product(name='Товар 1', price=100),
            Product(name='Товар 2', price=200),
        ], ignore_conflicts=True)
        self.stdout.write('База данных заполнена тестовыми данными')

Скрипт бэкапа для разработчика

#!/bin/bash
# db-backup.sh — запускайте из корня проекта
set -euo pipefail

BACKUP_DIR="./backups"
MAX_BACKUPS=10
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"

if [ -f "app.db" ]; then
    sqlite3 app.db ".backup '$BACKUP_DIR/sqlite_$TIMESTAMP.db'"
    echo "SQLite бэкап создан"
fi

if command -v pg_dump >/dev/null 2>&1; then
    if pg_dump -h localhost -U postgres -Fc -f "$BACKUP_DIR/pg_$TIMESTAMP.dump" mydb; then
        echo "PostgreSQL бэкап создан"
    else
        echo "Ошибка pg_dump" >&2
        rm -f "$BACKUP_DIR/pg_$TIMESTAMP.dump"
    fi
fi

# Оставляем последние MAX_BACKUPS файлов каждого типа
ls -t "$BACKUP_DIR"/*.db 2>/dev/null | tail -n +$((MAX_BACKUPS+1)) | xargs -r rm -f
ls -t "$BACKUP_DIR"/*.dump 2>/dev/null | tail -n +$((MAX_BACKUPS+1)) | xargs -r rm -f

Инструменты

  • DBeaver (бесплатный, все популярные СУБД) — экспорт в SQL, CSV, Excel, удобен для разовых ручных выгрузок. dbeaver.io
  • TablePlus (macOS, Windows, Linux) — лёгкий клиент с быстрым экспортом, есть бесплатная версия. tableplus.com
  • DataGrip (JetBrains, платный) — дампы с настройками, сравнение схем. jetbrains.com/datagrip

Версии локальной базы в dbsend

Если хочется хранить копии не только в папке backups/, а вне машины и с историей, подойдёт dbsend для SQLite. CLI сам делает согласованную копию SQLite-файла через VACUUM INTO, загружает её как новую версию и не создаёт дубль, если файл не изменился:

npm install --global @dbsend/sdk
dbsend login --key sqv_pk_...
dbsend init --database <id> --engine sqlite   # создаст .dbsend.json без секретов

dbsend backup ./app.db -l before-migration
dbsend log
dbsend restore <snapshotId> ./app.restored.db --yes

Для PostgreSQL и MySQL загружается plain SQL-дамп (-e postgresql_dump или -e mysql_dump), сам дамп вы делаете pg_dump/mysqldump. Все команды — в документации CLI.

Правило 3-2-1 для локальной разработки

  • 3 копии данных;
  • 2 разных носителя (локальный диск + облако);
  • 1 копия вне машины (облачный диск, S3).

Для разработки хватает упрощённой версии: копии в папке проекта и регулярная синхронизация с облаком.

Хранение бэкапов в облаке из командной строки

Google Drive через rclone

# разовая копия
rclone copy ./backups gdrive:dev-backups/myproject/

# зеркало папки: файлы, удалённые ротацией локально, удалятся и на Drive
rclone sync ./backups gdrive:dev-backups/myproject/

Яндекс Диск через WebDAV

curl -T backup.sql \
  "https://webdav.yandex.ru/dev-backups/backup_$(date +%Y%m%d).sql" \
  -u "login:app_password"

Используйте пароль приложения, а не основной пароль аккаунта.

Чеклист

  • Перед любой миграцией — бэкап базы.
  • Перед docker compose down -v — проверьте, нужны ли данные из томов.
  • Перед экспериментами с данными — бэкап.
  • После долгого наполнения базы тестовыми данными — бэкап.
  • При активной разработке — автоматический бэкап раз в день.
  • Раз в неделю проверяйте, что бэкап восстанавливается: см. как проверить, что бэкап рабочий.

Частые вопросы

Нужно ли бэкапить локальную базу, если в проекте есть миграции?

Да. Миграции восстанавливают схему, но не данные. Тестовые строки, которые вы вносили вручную, без бэкапа исчезнут.

Можно ли добавить SQLite-файл в Git?

Технически можно, но не стоит: бинарный файл плохо версионируется, а конфликты в нём не разрешить. Добавьте .db и .dump в .gitignore и пишите seed-скрипты.

Как быстро сделать бэкап PostgreSQL перед миграцией?

pg_dump -h localhost -U postgres -Fc -f pre_migration.dump mydb — одна команда. Для небольшой локальной базы она выполняется практически мгновенно.

Что делать, если docker compose down -v уже выполнен?

Если данные были в томе Docker, а не в bind mount, восстановить их, как правило, нельзя. На будущее держите данные в папке на хосте и делайте дамп перед опасными командами.

Сколько хранить локальные бэкапы?

Для активной разработки обычно хватает последних 5–10 копий — скрипт выше удаляет остальные автоматически.