Guides / SQLite in production: database is locked, or data disappears

SQLite in production: database is locked, or data disappears

SQLite worked perfectly in development. In production you get sqlite3.OperationalError: database is locked, attempt to write a readonly database, or worse: the data you entered yesterday is gone after a deploy. The problem isn't SQLite itself, it is where and how your host runs the app.

What it means

SQLite keeps everything in one file and allows one writer at a time. That's fine for one process on one disk. A production setup typically has several worker processes, sometimes several instances, and often a disk that is replaced on every deploy.

Why it happens

  1. The disk is ephemeral. On containers and most platforms, files you write are lost when the app restarts or redeploys. Your db.sqlite3 goes with them.
  2. The database is read-only. The web server's user can read the file but not write to it, or can't write to the folder it lives in (SQLite needs to create journal files next to it).
  3. "Database is locked": a second writer waits longer than the default timeout (5 seconds), usually a slow transaction while another worker writes.
  4. Several instances each have their own copy of the file, so users see different data depending on which instance answers.
  5. A network filesystem. SQLite locking is unreliable over NFS and similar mounts.

The fix

Recommended: move to Postgres

For anything with real users, use a real database server. Export from SQLite and load into Postgres:

# 1. with the old SQLite settings:
python manage.py dumpdata --natural-foreign --natural-primary \
  -e contenttypes -e auth.Permission --indent 2 > data.json

# 2. switch DATABASES to Postgres, then:
python manage.py migrate
python manage.py loaddata data.json

Check that counts match before you retire the file. For large or complicated databases, pgloader migrates the data directly. Related setup: installing a Postgres driver and connecting to it.

If you must stay on SQLite (one instance, a persistent disk)

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": os.environ.get("SQLITE_PATH", BASE_DIR / "db.sqlite3"),
        "OPTIONS": {
            "timeout": 20,  # seconds to wait for a lock
        },
    }
}

On Django 5.1 and newer you can also enable write-ahead logging and take the write lock at the start of a transaction, which removes most "locked" errors under moderate load:

"OPTIONS": {
    "timeout": 20,
    "transaction_mode": "IMMEDIATE",
    "init_command": "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;",
},

Back up the file on a schedule with sqlite3 db.sqlite3 ".backup backup.sqlite3". Don't copy it while the app is writing.

Making it stick in production

Or skip it

SQLite isn't suitable on Django Cloud because the app's disk is reset on every deploy. You add a managed Postgres database from the dashboard and your app is connected to it.

pip install djangocloud-cli
djangocloud deploy

Get startedSee pricing

Free plans are available for students, AWS Activate startups, open-source, non-profit and research projects. See how Django Cloud compares with other ways to host Django.

Django is a registered trademark of the Django Software Foundation. Django Cloud is not affiliated with or endorsed by it. Found a mistake in this guide? Tell us and we will fix it.