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
- The disk is ephemeral. On containers and most platforms, files you write are lost when the app restarts or redeploys. Your
db.sqlite3goes with them. - 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).
- "Database is locked": a second writer waits longer than the default timeout (5 seconds), usually a slow transaction while another worker writes.
- Several instances each have their own copy of the file, so users see different data depending on which instance answers.
- 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)
- Put the file on a persistent volume that is not replaced on deploy, and give the app's user write access to the file and its folder.
- Run one instance, and few workers.
- Raise the timeout so short contention doesn't error:
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
- Decide where state lives before you deploy: anything that must survive a restart belongs in a database or object storage, not in the app's folder.
- Keep test and production on the same database engine, so you find engine differences before users do.
- Run
python manage.py check --deployand make a restore from backup part of your routine.
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
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.