I recently set up automated backups for a Rails application running SQLite in production. The setup uses Litestream to continuously replicate the database to Cloudflare R2 storage.
For those unfamiliar, Litestream streams SQLite changes to an S3-compatible bucket in near real-time. Cloudflare R2 works nicely here as it’s S3-compatible and has generous free tier limits. The litestream-ruby gem handles the integration with Rails and Puma.
Setting up Cloudflare R2
First we need a bucket and API credentials in Cloudflare.
Create a new R2 bucket. I’d recommend naming it something like your-app-name--backups to keep things organised.
Next, create a new “User API Token” with the following settings:
- Name it whatever makes sense to you, something like
Your App Name - Backups - Set permissions to “Object Read & Write”
- Apply to specific buckets only and select your newly created bucket
Take note of the credentials that are generated:
- The “Access Key ID” - looks like
y43hqiefniu34y - The “Secret Access Key” - looks like
enu3yb732yiu3yxeiuy23bbi7yb3uxyiyu - The “jurisdiction-specific endpoint” URL - looks like
https://1a2b3c4d5e.r2.cloudflarestorage.com
Keep these safe, we’ll need them in the next step.
Storing credentials in Rails
Assuming you’re already familiar with Rails encrypted credentials, open the production credentials file:
EDITOR="code --wait" rails credentials:edit --environment=production
Add your Litestream credentials:
# config/credentials/production.yml.enc
# Note these credentials are completely made up - keyboard mashing
litestream:
replica_bucket_name: your-app-name--backups
replica_bucket_ext_url: 1a2b3c4d5e.r2.cloudflarestorage.com
replica_key_id: y43hqiefniu34y
replica_access_key: enu3yb732yiu3yxeiuy23bbi7yb3uxyiyu
Installing the Litestream gem
Follow the litestream-ruby setup:
bundle add litestream
rails generate litestream:install
This will generate the config files we need to modify.
Configuring Puma
Add the Litestream plugin to your Puma config so it starts automatically with your web server:
# config/puma.rb
# Run litestream only in production.
plugin :litestream if ENV.fetch("RAILS_ENV", "production") == "production"
Configuring Litestream
The generator creates a config/litestream.yml file that needs some adjustments for Cloudflare R2.
Remove any access-key-id or secret-access-key entries from the file - we’ll be setting these via the initializer instead.
- access-key-id: $LITESTREAM_ACCESS_KEY_ID
- secret-access-key: $LITESTREAM_SECRET_ACCESS_KEY
Update the bucket values to use your bucket name:
- bucket: $LITESTREAM_REPLICA_BUCKET
+ bucket: your-app-name--backups
Add the endpoint configuration. This is needed because we’re using Cloudflare R2 rather than Amazon S3:
+ endpoint: $LITESTREAM_REPLICA_ENDPOINT
You can remove the production_cache and production_cable database configs unless you specifically want to back those up. I don’t see much value in backing up cache or cable databases.
The final config/litestream.yml should look like:
# config/litestream.yml
dbs:
- path: storage/production.sqlite3
replicas:
- type: s3
endpoint: $LITESTREAM_REPLICA_ENDPOINT
bucket: your-app-name--backups
path: storage/production.sqlite3
- path: storage/production_queue.sqlite3
replicas:
- type: s3
endpoint: $LITESTREAM_REPLICA_ENDPOINT
bucket: your-app-name--backups
path: storage/production_queue.sqlite3
Setting up the initializer
The generator also creates config/initializers/litestream.rb. Uncomment and update the following lines:
# config/initializers/litestream.rb
# Configuration condensed for brevity.
Rails.application.configure do
# Configure Litestream through environment variables. Use Rails encrypted credentials for secrets.
litestream_credentials = Rails.application.credentials.litestream
ENV["LITESTREAM_REPLICA_ENDPOINT"] = litestream_credentials&.replica_bucket_url
config.litestream.replica_bucket = litestream_credentials&.replica_bucket_url
#
# Replica-specific authentication key. Litestream needs authentication credentials to access your storage provider bucket.
config.litestream.replica_key_id = litestream_credentials&.replica_key_id
#
# Replica-specific secret key. Litestream needs authentication credentials to access your storage provider bucket.
config.litestream.replica_access_key = litestream_credentials&.replica_access_key
end
The awkward bit here is the ENV["LITESTREAM_REPLICA_ENDPOINT"] line. We’re setting an environment variable because the config/litestream.yml reads from $LITESTREAM_REPLICA_ENDPOINT. For some reason the config.replica_endpoint option doesn’t work with Cloudflare R2, so this workaround gets the endpoint into the YAML config where it’s needed.
Ship it
There we have it, continuous SQLite backups streaming to Cloudflare R2. Once deployed, Litestream will start replicating your database changes automatically whenever Puma starts.
If you need to restore from a backup, the litestream-ruby gem provides rake tasks to help with that - check the gem documentation for details.