Skip to content

Laravel Ships SQLite by Default. When Is That Actually Fine?

The default changed and a lot of applications went to production on it without anyone deciding to. The cases where that is correct are real and narrower than the default implies.

4 min read

Laravel's default database is SQLite, and defaults are powerful. A meaningful number of applications now reach production on it because nobody changed the line, rather than because someone decided it was right.

Sometimes it is right. This is where the line actually falls.

What is genuinely different

Not the SQL. Not Eloquent, which behaves the same. One thing: SQLite is a file that your process opens, not a server your process connects to.

Everything follows from that. There is no connection limit because there are no connections. There is no network latency because there is no network. And writes are serialised across every process touching the file, because a file has no query planner arbitrating between clients.

Turn on WAL before anything else. Without it, a reader blocks a writer and a writer blocks readers, which is the configuration most people benchmark without knowing:

// a migration, or a service provider
DB::statement('PRAGMA journal_mode = WAL');
DB::statement('PRAGMA busy_timeout = 5000');
DB::statement('PRAGMA foreign_keys = ON');

The three of those are close to mandatory. WAL lets readers proceed during a write. The busy timeout turns an instant database is locked error into a short wait, which is almost always what you wanted. And foreign keys are off by default in SQLite, which surprises people who wrote constrained() in a migration and assumed it was being enforced.

Where it is the right answer

An internal tool. Twenty people, mostly reading. The operational saving is real: no database server to patch, back up, secure or pay for, and a backup is a file copy.

A single-server application with modest writes. A content site, a booking page, a small SaaS in its first year. SQLite on local disk is faster than Postgres over a network for a single reader, because it is not crossing a socket.

Tests. An in-memory SQLite database makes a suite fast, with the caveat below.

Anything embedded or edge-deployed, where a database server is not available at all.

Where it is not

Several application servers. This is the hard boundary. Two web servers cannot share a SQLite file safely over a network filesystem. If you will ever scale horizontally, this decision has to be made again, and making it later is a migration under time pressure.

Write-heavy work. Anything logging every request, ingesting events, or processing a queue in the database. Writes serialise; that is the design, not a tuning problem.

More than a couple of queue workers. Workers polling a jobs table are concurrent writers. Use Redis for the queue and SQLite for the application, or run one worker and accept the throughput.

Anything needing point-in-time recovery, replication, or a read replica. There are tools that add replication on top of SQLite and they are a dependency and a decision, not a default.

The testing trap

The most common SQLite decision in a Laravel codebase is not the production one. It is :memory: in phpunit.xml, chosen for speed, against an application running MySQL or Postgres.

That suite cannot catch:

  • A constraint violation the real engine enforces and SQLite does not.
  • A collation difference, which is the case-sensitivity trap in another hat.
  • A migration that locks a large table, because there is no lock and no table.
  • Anything using a JSON operator, a window function or a type the real engine has and SQLite does not.

It is a reasonable trade for a unit-heavy suite and a bad one as the only thing standing between you and a schema mistake. Run the fast suite on SQLite if you like; run the suite that gates a deploy against the engine you deploy to.

Deciding honestly

Ask one question: will this application ever run on more than one server?

If the answer is no and you know why it is no - it is internal, it has a ceiling, it is genuinely small - SQLite is not a compromise. It is less infrastructure to own, and less infrastructure to own is a real benefit that gets dismissed too readily.

If the answer is yes, or is "probably not but who knows", start on the engine you would end on. Migrating a live application is work you can avoid entirely by spending ten minutes on this in week one - which is the same argument as money and dates, and it holds for the same reason.

Related questions

Is SQLite a real database?
Entirely, and it is probably the most deployed one in the world. The question is never whether it is serious software - it is whether its concurrency model fits your write pattern, because that is the one dimension where it differs from a client-server database in a way you cannot configure away.
What breaks first at scale?
Concurrent writes. SQLite serialises them, so a second writer waits. With WAL enabled readers do not block and a write-heavy endpoint under real traffic still queues behind itself - and the symptom is a database lock timeout rather than a slow query, which sends people looking in the wrong place.
Can we run it with queue workers?
Carefully, and usually not with the database queue driver. Several workers polling a jobs table are several concurrent writers against a database designed for one at a time. Use Redis for the queue, or accept a worker count of one.
How do we move off it later?
The schema and Eloquent code move almost unchanged; the data needs exporting and the loose typing needs checking. SQLite will accept a string in an integer column, so a dump can contain values the destination refuses. Validate before you migrate rather than during.

← Back to all articles

Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page