Backing up a live SQLite database: use the backup API, not a file copy
Copying a SQLite file while an application may write to it can give you a broken copy. Use the database's own backup interface, in small steps, and count the space for the file and its write-ahead log. A file that has not changed for days can still be written to mid-copy.
π― For A developer or operator who keeps application state in SQLite files
- Why a plain copy of a live database file is unsafe
- How an online backup in pages works
- Which space and rotation mistakes fill a disk
π Many small databases
Our system keeps its state in separate SQLite files, one per module: traces, a ledger, caches, queues. On 3 November 2026, 21:53 UTC the data folder held 128 database files totalling 12.0 GB. That is a normal and pleasant way to build small services, and it means that backup is a job you must get right many times over.
β οΈ Why a copy is not a backup
A SQLite database is one file plus, in write-ahead mode, a second log file. A writer may be in the middle of a transaction while you copy. Copy the main file and then the log at different moments and you hold two halves of different times. The result may open and may even look fine, and contain missing or inconsistent rows.
One reviewer objection in October 2026 made this concrete. A file that "has not changed in a long time" is not safe to copy: a writer can begin a transaction while the copy is running. Age of the file is not a lock.
β The safe way
SQLite ships an online backup interface. It copies the database page by page into a destination file and keeps the result consistent even if the source is written to during the copy. Our backup script uses it with a small page count per step and a short sleep between steps, so a big database does not block writers for long. We run the copies one after another, not in parallel, to keep the disk and the load calm.
Two accounting rules came out of the 14 September 2026 incident, when the disk reached ninety-seven percent because backups had no rotation. First, count the space for the file plus its log, in the same pass that makes the copy. Second, the guard's own comment names missing rotation as the cause, so rotate old copies in the same job that makes new ones, and the job cannot grow without bound.
π οΈ Practice: build the backup job
- List your database files and mark which are live. Include the write-ahead log files in the size.
- Write a backup script that opens each source, opens a destination file and runs the backup interface in steps of a few hundred pages with a short pause.
- Run the databases one by one. Log the size of each copy and the time it took.
- After each copy open the destination and run an integrity check. Record the result.
- Keep a fixed number of recent copies and delete older ones in the same run.
- Wrap the job with the load-shedding wrapper so that it does not start on a busy host.
- Once a month restore one copy into a scratch folder and read a few rows.
β Check yourself
- Start a writer that inserts rows in a loop. Back up while it runs. The copy passes the integrity check.
- Count the disk space your backups use today and next month. It does not grow without limit.
- Restore the latest copy to a scratch folder. The application opens it and reads recent data.
- The job log says how long each database took and whether the check passed.
π§ When this does NOT work
- Very large databases. A step-wise copy of hundreds of gigabytes takes hours. Plan the window and the disk.
- Heavy write load. A busy writer can make the backup restart pages many times. Back up at a quiet hour.
- Same disk. A backup on the disk you are protecting does not survive a disk loss. Copy it off the machine.
- No restore test. An unverified backup is a hope. Test restores on a schedule.
- Other stores. Files outside SQLite, such as uploads, need their own backup.
π Evidence
| What we measured | Count and total size of database files in our data folder, and the use of the backup interface in our scripts |
| When | 3 November 2026, 21:53 UTC |
| Artifact | The data folder of our private deployment (128 files, 12.0 GB) and the sequential backup script |
| Limit | The size shows how much there is to protect, not that every copy has been restore-tested. The 14 September incident comes from our notes |
β‘οΈ Next
Plan restoring in Recovery after a failed, stuck or uncertain run, and keep the job from starving production with load shedding for scheduled jobs.
Sources
- Recovery after a failed, stuck or uncertain run: restoring.
- Permissions and data flow: what is stored where.
- An AI incident playbook for a small team: incident steps.
- Agent task receipts: what they prove: records of runs.
π Article connections
Open the full knowledge graph βPublished article links. Select a node to open its article at the heading.
Article links Β· keyboard / text view
Same topic
- A vector base is a map, not a source: what we learned from retrieval
- When you cannot tell what a model call cost: freeze, then reconcile
- Splitting a big task for a cheap model: the 24,000-byte lesson
- Skills, MCP tools and rules: three ways to give an agent capabilities
- Eight shell and git mistakes our agents made, and the rule for each
- Several agents, one repository: ownership rules from real incidents
Next steps
Related reading
A vector base is a map, not a source: what we learned from retrieval
A vector base finds passages that are close in meaning to your question. It does not know which are true or current. Treat every hit as a pointer: open the file it names, check the hash, and take numbers from the artifact, never from the retrieved paragraph.
AI agents and MCP explainedWhen you cannot tell what a model call cost: freeze, then reconcile
Sometimes a model call times out and you do not know whether the provider charged you. Never record that as zero. Mark it uncertain, stop new paid calls, and let a named person reconcile it with the provider's statement. Refusals before generation are the exception.
AI agents and MCP explainedSplitting a big task for a cheap model: the 24,000-byte lesson
A cheap model does well on one small, well-defined job and badly on a big pile. We cap a task at 24,000 bytes and refuse larger ones before any model is called. Split by unit, one file or one country per task, and check the size before you start.
AI agents and MCP explainedSkills, MCP tools and rules: three ways to give an agent capabilities
Agents get abilities in different ways. A rule constrains what they do, a skill tells them how to do a job, and an MCP tool lets them call a function. Each has its own risk, so declare all three in a register and test that nothing exists unlisted.
π¬ Comments