One Shot runs SQLite in production, not just in development. That's a deliberate choice for a kit
meant to run on a single small server, not a limitation you're expected to migrate away from the
moment you launch.

## SQLite in production

Rails 8's SQLite adapter is production-ready, and it's what backs the database along with Solid
Queue, Solid Cache, and Solid Cable, none of which need a separate Redis or Postgres service. The
database files live in a Docker volume Kamal manages (`oneshot_storage` in this kit's own
deployment), which persists across deploys since the container itself is disposable but the volume
isn't.

**Back this volume up, off the server, on a schedule.** It's the one piece of state a redeploy
can't recreate: your entire database and any locally stored uploaded files live inside it. A
server failure without an off-server backup means losing everything in that volume, not just the
running container.

## When SQLite stops being enough

SQLite in this configuration comfortably handles the traffic most single-server apps see. If you
outgrow a single server's disk and CPU for the database specifically, the standard path is moving
to a managed Postgres instance and swapping the adapter, which is a deliberate, plannable migration
rather than something you need to anticipate on day one. Most apps built on this kit won't hit that
ceiling before other constraints do.

## File storage: local disk versus S3

Active Storage, by default, writes uploaded files to local disk inside the same volume as the
database. That works, and it's the zero-configuration default. For production, configuring
S3-compatible object storage is worth doing before you have real user uploads to migrate:

- `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET`, `S3_REGION`
- `S3_ENDPOINT`, if you're using an S3-compatible provider other than AWS itself (Cloudflare R2 and
  similar services work through this same configuration)

See [Optional Integrations](/docs/bring-your-own-keys/optional-integrations) for where these are
set. Until you configure them, the local-disk volume is the fallback, and it works correctly; it
just means files live and get backed up alongside your database rather than in dedicated object
storage.

## Migrations

Run a one-off migration against the running production container directly through Kamal:

```bash
bin/kamal app exec "bin/rails db:migrate"
```

## Next

You've now covered the whole deployment picture. See what every environment variable in the app
actually does: [Bring Your Own Keys](/docs/bring-your-own-keys/secret-key-base).
