Docs · Deployment

Production Database and Storage

Running SQLite in production, the storage volume, and S3-compatible file storage.

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 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:

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.