Files
sqlite-backup-s3/README.md
T
vasyansk f166f95b44
docker-build / Build image (push) Successful in 11s
Add configurable retries for locked SQLite backups
Add SQLITE_TIMEOUT, BACKUP_RETRIES, and BACKUP_RETRY_DELAY
environment variables to handle "database is locked" errors during
backup. Retry the sqlite3 .backup command with configurable timeouts
and delays, and clean up any WAL/SHM files that may interfere with
subsequent attempts. Bump version to 1.2.2.
2026-09-01 12:15:38 +07:00

4.7 KiB
Raw Blame History

бекапирование sqlite в minio

services:
  sqlite_backup:
    image: git.realmanual.ru/pub/sqlite-backup-s3
    container_name: sqlite_backup
    restart: always
    volumes:
      - ./var/lib/data/:/data
    environment:
      - DB_FILE
      - CRONTAB
      - DELETE_AFTER
      - KEEP_LAST
      - TZ
      - SQLITE_TIMEOUT
      - BACKUP_RETRIES
      - BACKUP_RETRY_DELAY
      - MINIO_PATH
      - MINIO_ACCOUNT_ID
      - MINIO_APPLICATION_KEY
      - MINIO_ENDPOINT
      - MINIO_LOCATION
      - HEALTHCHECK_URL
      - HEALTHCHECK_UUID

пример .env

DB_FILE=/data/db.sqlite
CRONTAB="00 06 * * *"
DELETE_AFTER=10
KEEP_LAST=10
TZ=Asia/Novosibirsk
SQLITE_TIMEOUT=60000
BACKUP_RETRIES=5
BACKUP_RETRY_DELAY=30
MINIO_PATH=myminio://sqlite/master/
MINIO_ACCOUNT_ID=account
MINIO_APPLICATION_KEY=key
MINIO_ENDPOINT=https://s3.domain.ru
MINIO_LOCATION=ru-nsk

# необязательно: пинг в healthchecks.io (в т.ч. self-hosted)
HEALTHCHECK_URL=https://hc.domain.ru/ping
HEALTHCHECK_UUID=00000000-0000-0000-0000-000000000000
  1. myminio - системно! не менять
  2. проверьте настройки локации, не должно быть пусто
  3. DELETE_AFTER - в днях
  4. KEEP_LAST - минимальное число копий, которые сохраняются всегда

Ротация

Удаление старых бекапов работает по двум условиям одновременно:

  1. бекап старше DELETE_AFTER дней
  2. и при этом не входит в KEEP_LAST самых свежих копий

То есть последние KEEP_LAST бекапов не удаляются никогда, даже если они старше DELETE_AFTER дней. Это защищает от потери всех копий, если контейнер долго стоял и новые бекапы не создавались.

KEEP_LAST по умолчанию 10, если переменная не задана.

Заблокированная база

Если базу пишет живой процесс (grafana, или не добитый старый под), sqlite3 .backup падает с Error: database is locked.

Бекап это переживает: соединение открывается с .timeout SQLITE_TIMEOUT (мс) — sqlite ждёт освобождения блокировки вместо мгновенной ошибки. Если за это время писатель не отпустил базу, попытка повторяется BACKUP_RETRIES раз с паузой BACKUP_RETRY_DELAY секунд.

Переменная Default Что делает
SQLITE_TIMEOUT 60000 сколько мс ждать блокировку в рамках одной попытки
BACKUP_RETRIES 5 сколько попыток снять дамп
BACKUP_RETRY_DELAY 30 пауза между попытками, сек

Максимальное время ожидания с дефолтами: 60s * 5 + 30s * 4 = 7 минут. После этого — exit 1 и пинг /fail.

Если база залочена постоянно — это не про бекап, а про то, что старый под не умер. Ретраи только прикрывают короткие окна.

Healthchecks

Если HEALTHCHECK_URL пуст — пинги не отправляются, бекап работает как обычно.

Итоговый адрес: ${HEALTHCHECK_URL}/${HEALTHCHECK_UUID}

Момент Запрос
старт бекапа .../${HEALTHCHECK_UUID}/start
дамп снят, проверен PRAGMA integrity_check, запакован, залит в s3, размер в s3 сверен с локальным, старое удалено .../${HEALTHCHECK_UUID}
любая ошибка на любом шаге .../${HEALTHCHECK_UUID}/fail

Для self-hosted инстанса HEALTHCHECK_URL — это адрес его ping-эндпоинта, например https://hc.domain.ru/ping.

Как передаётся окружение в cron

crond из busybox запускает джобы с пустым окружением, поэтому entrypoint.sh сохраняет env контейнера в /tmp/container.env (права 600), а cron-запись вызывает /scripts/backup.sh --from-cron, который его подгружает. Ручной запуск (docker exec ... /scripts/backup.sh) использует то окружение, с которым его вызвали.