A bug tracker with no database server

Almost every self-hosted tracker asks you to install a database server before you can file a single bug. For a team of five, that second service is nearly all cost and almost no benefit. Here is the argument for a single file — and where it stops being true.

The short answer

A bug tracker for a small team is a read-heavy application with megabytes of data and a handful of concurrent users. SQLite handles that without breaking a sweat, and removes an entire service you would otherwise install, secure, monitor, upgrade and back up separately. The limits are real but distant: several app servers writing at once, network storage, or sustained heavy writes.

What the database server actually costs you

Installing MySQL or PostgreSQL is not difficult. That is the wrong measure, because you do it once and live with it for years. The cost is everything that comes after:

  • A second service to keep running. It can be down while your app is up, which produces the worst kind of outage — one where the page loads and then fails.
  • A second thing to upgrade. Major version upgrades have their own migration steps, on their own schedule, unrelated to your tracker.
  • A second thing to secure. A listening port, a user, a password, and a default configuration that usually wants tightening.
  • A second thing to back up, on a different mechanism from your uploaded files, which is how people end up with a database dump and no attachments.
  • Credentials to thread through deployment, which is a surprisingly common reason a self-hosted install never gets finished at all.

For an application serving two hundred people, all of that buys real capability. For a tracker holding four thousand bugs and serving six, it buys nothing you can point at.

"SQLite is a toy" is about fifteen years out of date

The reputation comes from a period when SQLite really did serialise writers crudely and locked aggressively. Write-ahead logging changed that: readers no longer block the writer and the writer no longer blocks readers. One writer at a time is still the model — but consider what a bug tracker's writes actually are.

ActionWritesHow often, realistically
Filing a bugOne row, plus attachmentsA few times a day
CommentingOne rowTens of times a day
Changing a statusOne row updateTens of times a day
Viewing anythingNoneConstantly

A busy day for a small team is perhaps a few hundred writes, each of them tiny, spread across eight hours. Those writes are measured in microseconds. The idea that they might contend with each other is arithmetic, not opinion: you would need orders of magnitude more traffic before the single-writer model became the thing slowing you down.

The database is not the bottleneck in a small team's bug tracker. The bottleneck is whether people file bugs at all.

What you get back

Removing the database server is not only about saving effort. It changes properties of the install that matter later:

  • Backup is a file. One consistent snapshot contains every project, bug, comment and role. Restoring is putting the file back.
  • Moving servers is a copy. No dump, no import, no version mismatch between the old server's dialect and the new one's.
  • Updates cannot half-apply across two systems. There is one thing to migrate, in one place, which is why BugTrack can dry-run a migration before touching anything live.
  • Reads are local. No connection pool, no network hop, no cold start — the data is a file the operating system has almost certainly cached.

Backing it up properly

One genuine trap: copying the database file while the application is running does not reliably work. In WAL mode the file on disk is not the whole picture, and a copy taken mid-write can be inconsistent.

The correct method is SQLite's own backup mechanism — the .backup command or the backup API — which takes a consistent snapshot while the app keeps serving. It is one command, it needs no downtime, and it produces a single file you can copy anywhere. Anything that tells you to stop the application first is working around the wrong problem.

When SQLite is genuinely the wrong answer

This is where the argument stops. SQLite is a poor fit when:

  • You need several application servers writing to one database. The model is one process with the file open; horizontal scaling breaks it.
  • The database lives on network storage. NFS and similar have historically unreliable locking semantics, and this is a real way to corrupt data.
  • Writes are heavy and sustained. Analytics ingestion, event streams, anything writing continuously rather than in human-paced bursts.
  • You need database-level access controls for several different applications sharing the same data.

None of those describe a bug tracker for a team that fits round one table. If your situation does look like one of them, you have outgrown this class of tool entirely and should choose accordingly — the same way you would choose Jira over something smaller once the organisation around the tracker changes shape.

Frequently asked questions

Is SQLite good enough for a team bug tracker?

For a small team, comfortably. A bug tracker is read-heavy, with a handful of concurrent users and a database measured in megabytes. WAL mode handles concurrent readers alongside a writer without difficulty at that scale.

What happens if two people file a bug at the same instant?

One write goes first and the other follows, microseconds later. Neither person notices. Serialised is not the same as slow.

Can I migrate to Postgres later if I outgrow it?

The data is standard SQL and exports cleanly, so the migration path exists. In practice, a team large enough to need it has usually outgrown the whole tool rather than just its storage layer.

One file, no second service

BugTrack stores everything in a local SQLite database. Node.js 18 and about 200 MB of disk is the entire requirement — no database server to install.

Download free →

Related reading