Litestream — Implementation

Scope: Litestream SQLite replication to Cloudflare R2 Tests: All passing (0 failures, 0 errors)


1. Gem Added

File: Gemfile

gem "litestream"
  • litestream — Bundles the Litestream binary and integrates with Puma as a plugin. No system package installation needed.

2. Configuration Files

2a. config/litestream.yml (NEW — generated + customized)

Litestream's native YAML configuration. Defines all 4 production databases and their S3-compatible replica targets. Customized from the generator output to add endpoint and force-path-style for Cloudflare R2 compatibility:

dbs:
  - path: storage/production.sqlite3
    replicas:
      - type: s3
        bucket: $LITESTREAM_REPLICA_BUCKET
        path: storage/production.sqlite3
        endpoint: $LITESTREAM_REPLICA_ENDPOINT
        force-path-style: true
        access-key-id: $LITESTREAM_ACCESS_KEY_ID
        secret-access-key: $LITESTREAM_SECRET_ACCESS_KEY
  # ... same pattern for production_cache, production_queue, production_cable

Key additions over the generated defaults:

  • endpoint: $LITESTREAM_REPLICA_ENDPOINT — required for Cloudflare R2 (non-AWS S3-compatible)
  • force-path-style: true — required for R2's path-style URLs

2b. config/initializers/litestream.rb (NEW — generated + customized)

Maps Rails encrypted credentials to the environment variables that config/litestream.yml references. Falls back to Cloudflare R2 credentials if dedicated Litestream credentials aren't set:

Rails.application.configure do
  litestream_credentials = Rails.application.credentials.litestream
  r2_credentials = Rails.application.credentials.cloudflare

  config.litestream.replica_bucket = litestream_credentials&.dig(:replica_bucket)
  config.litestream.replica_key_id = litestream_credentials&.dig(:replica_key_id) || r2_credentials&.dig(:r2_access_key_id)
  config.litestream.replica_access_key = litestream_credentials&.dig(:replica_access_key) || r2_credentials&.dig(:r2_secret_access_key)

  account_id = litestream_credentials&.dig(:r2_account_id) || r2_credentials&.dig(:r2_account_id)
  config.litestream.replica_endpoint = "https://#{account_id}.r2.cloudflarestorage.com" if account_id.present?

  config.litestream.username = litestream_credentials&.dig(:dashboard_username) || "admin"
  config.litestream.password = litestream_credentials&.dig(:dashboard_password)
end

3. Modified Files

3a. Modified: config/puma.rb

Added Litestream Puma plugin (runs replication inside the Puma process):

plugin :litestream if ENV["LITESTREAM_IN_PUMA"]

3b. Modified: config/routes.rb

Mounted Litestream dashboard behind admin authentication:

authenticate :user, ->(user) { user.admin? } do
  mount Litestream::Engine, at: "/litestream"
end

3c. Modified: config/deploy.yml

Added Litestream env var to Kamal clear env:

env:
  clear:
    LITESTREAM_IN_PUMA: true

4. Summary of Files

New Files (2)

File Purpose
config/litestream.yml Litestream replication config (4 DBs → R2)
config/initializers/litestream.rb Litestream credentials mapping & dashboard auth

Modified Files (4)

File Changes
Gemfile Added gem "litestream"
config/puma.rb Added Litestream Puma plugin
config/routes.rb Mounted Litestream dashboard at /litestream
config/deploy.yml Added LITESTREAM_IN_PUMA: true env var