## What makes it work now and did not before

Rails 8 sets the pragmas that used to be the reason people said no. Write-ahead logging lets readers
continue while a write is in flight, and a busy timeout makes concurrent writers wait rather than
raise. You can see the configuration in `config/database.yml`.

Solid Queue, Solid Cache and Solid Cable also use SQLite, which is why a One Shot deploy has no
Redis and no separate job host. Four databases, four files, one container, declared together in
`config/database.yml` and mounted by the volume line in `config/deploy.yml`.

```bash
bin/kamal app exec 'bin/rails runner "puts ActiveRecord::Base.connection.adapter_name"'
```

## Where the limit actually sits

Not at a row count, and not at a request rate. It sits at the number of machines that need to write.
One server handles a surprising amount of traffic, because a local file read has no network hop at
all. The moment you want two application servers behind a load balancer, the file cannot be shared,
and the answer is Postgres.

Watch for the signal rather than a number: sustained `SQLite3::BusyException` in the logs means
writes are queueing longer than the busy timeout, and that is the point to move.

## Back it up like a file, because it is one

```bash
bin/kamal app exec 'sqlite3 storage/production.sqlite3 ".backup /tmp/backup.sqlite3"'
```

Use `.backup` rather than copying the file directly. A plain `cp` of a database mid-write gives you
a corrupt copy, and you will not find out until you try to restore it.

## When to choose Postgres instead on day one

Three cases. You already know you need multiple application servers. You need an extension such as
PostGIS or pgvector. Or your team has an operational preference and someone else runs the database.
Outside those, starting on SQLite and moving later costs one migration, and most apps never do it.
