Пользователи, заказы или сессии обнулились после перезапуска или обновления бота на Bothost. Файл базы при этом «создаётся заново» без ошибки. Ниже почему так происходит с SQLite и куда писать файл, чтобы он пережил деплой.

Для людей и ИИ. Каноническая страница: https://bothost.ru/docs/sqlite-data-lost · Markdown: https://bothost.ru/docs/sqlite-data-lost.md · Индекс: https://bothost.ru/llms.txt
Отвечает на запросы: SQLite database disappeared, bot loses data after restart, database reset after deploy, bot.db empty, data lost on reboot, /app/data.
Как устроена папка и примеры создания таблиц: хранение базы данных.

Что на Bothost сохраняется

Между пересборками сохраняется каталог /app/data. Он подключён отдельным томом. Платформа задаёт переменную DATA_DIR=/app/data. Файл в другом месте живёт в слое контейнера или в каталоге, который при новом деплое заменяется кодом из Git:
  • /app/bot.db, ./bot.db, database.sqlite рядом со скриптом;
  • /tmp/...;
  • путь внутри репозитория, который приходит из коммита;
  • база :memory:.
Кнопка перезапуска того же контейнера часто оставляет файлы на месте, поэтому кажется, что «вчера после рестарта всё было». Обновление из Git, загрузка нового архива и пересоздание контейнера заново собирают файловую систему кода. Том /app/data при этом остаётся, всё остальное пропадает. Пустая база выглядит как пропажа: SQLite не сообщает об ошибке, а создаёт новый файл. Проверка в панели: откройте файловый менеджер бота и найдите базу именно в папке data. Если после сообщений пользователей файла там нет, процесс пишет мимо тома.

SQLite database disappeared

Относительный путь

sqlite3.connect("bot.db")
Файл создаётся в текущем каталоге процесса. На Bothost это обычно /app/bot.db, а том смонтирован в /app/data. После деплоя в /app снова лежит код из репозитория, старого bot.db там нет, библиотека создаёт пустой. Пишите в каталог данных:
import os
import sqlite3
from pathlib import Path

data_dir = Path(os.environ.get("DATA_DIR", "/app/data"))
data_dir.mkdir(parents=True, exist_ok=True)
db_path = data_dir / "bot.db"

conn = sqlite3.connect(db_path)
conn.execute(
    "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)"
)
conn.commit()
Один и тот же путь должен быть во всех местах кода. Второй модуль со своим connect("bot.db") или connect("/tmp/bot.db") заводит другую, пустую базу, и кажется, что данные «то есть, то нет». Задайте путь одной переменной в панели, если так удобнее читать код: DATABASE_PATH=/app/data/bot.db.

База в Git затирает живые данные

Файл bot.db закоммичен в репозиторий. При обновлении код из Git копируется в приложение, и вместе с ним приезжает старая или пустая база из коммита. Папку data в Git не кладут. Добавьте в .gitignore:
data/
*.db
*.sqlite
*.sqlite3
Живой файл храните только в /app/data. Если база уже была в корне репозитория, перенесите её один раз через файловый менеджер панели в data, поправьте путь в коде и уберите файл из коммита, иначе следующее обновление вернёт старую копию.

:memory: и новый файл на каждый процесс

sqlite3.connect(":memory:") существует, пока живёт этот процесс. Рестарт контейнера начинает с пустой базы. То же самое делает код, который при каждом старте удаляет файл и создаёт таблицы заново без проверки. CREATE TABLE IF NOT EXISTS таблицу сохраняет. DROP TABLE и os.remove(db_path) при каждом запуске данные стирают, даже если путь правильный.

bot loses data after restart

Файл в /app/data на месте, а последней записи нет. Не было commit. execute без commit() держит изменение в транзакции. Процесс убили рестартом, транзакция откатилась. Фиксируйте изменения, которые нельзя потерять:
conn.execute("INSERT INTO users (name) VALUES (?)", (name,))
conn.commit()
Рядом лежат служебные файлы WAL. При режиме WAL SQLite пишет bot.db-wal и bot.db-shm. Если скопировали в другое место только bot.db, последние записи остаются в -wal. Для работы на томе копировать ничего не нужно: все три файла и так в /app/data. В резервную копию забирайте каталог целиком, либо перед копированием выполните checkpoint. Рестарт случился в середине записи. Короткая транзакция и commit() на каждое законченное действие уменьшают потерю до последней незафиксированной операции. Отдельный сервер MySQL или PostgreSQL имеет смысл, когда базы много и писателей несколько. Сам файл SQLite на томе /app/data для одного процесса бота как раз рассчитан на рестарты контейнера. Права на каталог образ выставляет при сборке. Если в логе attempt to write a readonly database или unable to open database file, процесс не может создать файл в выбранном пути. Верните путь на /app/data/... и задеплойте снова. Разбор через терминал панели: SQLite в терминале.

Как понять, что путь верный

    • В логе старта напечатайте абсолютный путь: print(db_path).
    • Строка должна начинаться с /app/data/.
    • После сообщения боту файл виден в файловом менеджере в папке data и его размер больше нуля.
    • Сделайте обновление из Git или залейте новый архив и откройте ту же базу. Старые строки на месте.
Пока путь в логе /app/bot.db или bot.db, следующий деплой базу обнулит, даже если прямо сейчас после кнопки «перезапуск» данные ещё видны.

См. также

Файл в /app/data есть, а записей нет? Пришлите в поддержку строку пути из лога и момент, после которого данные пропали: @bothostru.