botdepobotdepoВсе статьи

2 октября 2026 г.

sqlite3.OperationalError: unable to open database file и Read-only file system: куда бот может писать файлы

Бот стартует и тут же падает на подключении к базе: sqlite3.OperationalError: unable to open database file. Или при сохранении JSON: OSError: [Errno 30] Read-only file system, PermissionError: [Errno 13]. Локально всё работало, база создавалась сама.

Что значит эта ошибка

SQLite хранит базу в обычном файле. Чтобы его открыть или создать, процессу нужно право писать в папку, где этот файл лежит. unable to open database file — это не «база сломана», а «не могу создать или открыть файл по этому пути». Папки нет, путь не тот или писать туда нельзя.

Реальный пример:

DB_PATH = "data/users.db"
...
  File "/app/01bot.py", line 42, in init_db
    conn = sqlite3.connect(DB_PATH)
sqlite3.OperationalError: unable to open database file

Причины по убыванию вероятности

  1. Папка из пути не существует. SQLite создаёт файл, но не создаёт папки. Дома data/ когда-то создали руками, а в git пустые папки не попадают, поэтому на сервере её нет.
  2. Писать рядом с кодом нельзя. На нормальном хостинге код бота лежит на файловой системе только для чтения: так бот не может испортить сам себя, а каждый деплой начинается с чистого кода. Любая запись рядом с .py файлами даёт Read-only file system или ту же unable to open database file.
  3. Относительный путь считается не от того места. "bot.db" отсчитывается от текущей папки процесса, а не от папки скрипта. Запустили из другой директории, и база создалась (или не создалась) не там.
  4. Нет прав. Файл или папка принадлежат другому пользователю (часто после запуска через sudo).

Как исправить

  1. Вынесите путь в переменную окружения с разумным значением по умолчанию:
    import os
    DB_PATH = os.getenv("DB_PATH", "/data/bot.db")
    os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
    conn = sqlite3.connect(DB_PATH)
    makedirs закрывает первую причину, а переменная позволяет дома указать DB_PATH=bot.db, не трогая код.
  2. То же самое со всем, что бот пишет: JSON со списком пользователей, загруженные фото, логи в файл.
    DATA_DIR = os.getenv("DATA_DIR", "/data")
    USERS_FILE = os.path.join(DATA_DIR, "users.json")
  3. Если хотите путь «рядом со скриптом» (для локального запуска), стройте его от __file__, а не от текущей папки:
    BASE = os.path.dirname(os.path.abspath(__file__))
    DB_PATH = os.path.join(BASE, "bot.db")
    Но на сервере с read-only кодом это всё равно не сработает, там нужна отдельная папка для данных.
  4. Для aiosqlite, SQLAlchemy (sqlite:////data/bot.db, четыре слеша — абсолютный путь), peewee и прочих правило то же.

Как это устроено в botdepo

Код бота доступен только для чтения. Писать можно в одну папку, /data: она сохраняется между деплоями и перезапусками, поэтому база не обнуляется при каждом обновлении кода. Укажите путь внутри /data, как в примере выше, и всё. Если бот упадёт на записи файла, botdepo распознает это в логе и подскажет, куда перенести путь. Задеплоить бота.

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

Почему бы не закоммитить bot.db в репозиторий?

База поменяется с первым же пользователем, и следующий деплой затрёт её версией из git. Плюс в публичном репозитории окажутся данные ваших пользователей. Добавьте *.db в .gitignore.

SQLite вообще нормально для бота?

Для бота на сотни и тысячи пользователей — да, вполне. Главное, чтобы файл лежал в постоянном месте и бот был запущен в одном экземпляре.