Answers · Deploying

How do I back up a SQLite database on a live server?

Run sqlite3 storage/production.sqlite3 ".backup /tmp/out.sqlite3" inside the container, then copy the result off the host. The .backup command takes a consistent snapshot while writes continue; a plain cp of a live database gives you a file that looks fine and fails on restore. Store it somewhere the server cannot reach.

The command that is safe to run while the app is serving

bin/kamal app exec 'sqlite3 storage/production.sqlite3 ".backup /tmp/backup.sqlite3"'
ssh your-server 'docker cp $(docker ps -qf name=web):/tmp/backup.sqlite3 ~/backup.sqlite3'
scp your-server:~/backup.sqlite3 ./backup-$(date +%F).sqlite3

Three steps because there are three boundaries: inside the container, on the host, and off the machine. The third one is the one people skip, and it is the one that matters.

Why cp is not good enough

SQLite writes in pages, and a write-ahead log holds changes that are not yet in the main file. A cp catches whichever pages happened to be flushed at that instant, plus a -wal file you probably did not copy. The result opens without complaint and is missing recent writes, or is corrupt in a way you discover during a restore at the worst possible moment.

.backup uses SQLite's own backup API, which takes a consistent snapshot across both files.

Verify the backup, because an unverified backup is a guess

sqlite3 backup-$(date +%F).sqlite3 'PRAGMA integrity_check;'
sqlite3 backup-$(date +%F).sqlite3 'SELECT COUNT(*) FROM accounts;'

integrity_check should print ok. A row count you recognize tells you it is the right database and not an empty one, which is the other failure people discover late.

Make it happen without you

A backup you run by hand happens until the week you are busy. Put it in a job and schedule it with Solid Queue: a class under app/jobs, listed in config/recurring.yml. The same container runs it, so there is nothing new to deploy. Upload the result to object storage with the S3 credentials that config/storage.yml already reads.

What this does not cover

Uploaded files. They live on the same volume but are not in the database, so a database backup misses them entirely. If you are storing attachments on local disk rather than S3, back up storage as well.

Related questions

Can I run SQLite in production for a Rails app?

Yes, for one server. Rails 8 ships SQLite configured for production use, and One Shot runs on it: the database is a file on a mounted volume, backups are a file copy, and there is no second service to operate. The limit is real though. Two application servers cannot write to the same SQLite file, so horizontal scaling is where it stops.

How do I deploy a Rails 8 app without Heroku?

Use Kamal, which ships with Rails 8. You give it a server over SSH and a container registry in config/deploy.yml, run kamal setup once, and kamal deploy after that. It builds an image, pushes it, and swaps containers with no downtime. A $5 VPS replaces the PaaS.

How do I deploy a Rails app from GitHub Actions?

Run kamal deploy as a workflow step. Kamal needs three things in the runner: an SSH key that can reach the server, registry credentials, and RAILS_MASTER_KEY. All three go in as repository secrets and are read by .kamal/secrets. Gate the deploy job behind the test job so a red build never ships.