# бекапирование sqlite в minio ```yaml 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 ```bash 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`. ## Планировщик `crond` из busybox не используется: под non-root uid (обычная ситуация в kubernetes с `securityContext.runAsUser`) он не загружает ни одного crontab и не сообщает об этом — в логе бесконечно идёт `wakeup dt=60`, а бекапов нет. Вместо него `entrypoint.sh` содержит свой планировщик на sh. Что это даёт: * работает под любым uid; * окружение наследуется напрямую, без выгрузки env в файл; * бекапы никогда не накладываются друг на друга — следующий запуск возможен только после завершения предыдущего; * некорректный `CRONTAB` роняет контейнер на старте с внятным сообщением (`[scheduler] FATAL: minute: '60' is outside 0-59`), а не приводит к тихому простою. Синтаксис `CRONTAB` — стандартные пять полей: `*`, `5`, `1,15`, `2-6`, `*/15`, `8-18/2`, `30/5`. В месяцах и днях недели понимаются имена (`jan`, `mon`). Воскресенье — и `0`, и `7`. Если ограничены одновременно день месяца и день недели, запуск происходит при совпадении любого из них — как в `crontab(5)`. При старте контейнера бекап делается сразу, эта минута считается отработанной. Расписание видно в логе при запуске: ``` [scheduler] schedule: 30 07 * * * (TZ=Asia/Novosibirsk, now 2026-09-02 16:45:21 +0700) [scheduler] expanded: min=[30] hour=[7] dom=[1 2 3 ...] mon=[1 2 ...] dow=[0 1 ...] day-match=and ``` Ручной запуск: `docker exec ... /scripts/backup.sh` — использует то окружение, с которым его вызвали. Период опроса часов — 20 секунд, меняется через `POLL_INTERVAL` (секунды).