
Every indie developer knows they should back up their database. Most of us set up something in a hurry during launch week, feel a brief glow of virtue, and never look at it again. The problem is that a backup you have never restored is not a backup. It is a hope. When your production database gets corrupted at 2am, hope is not going to help you.
The good news is that building a reliable backup and restore workflow for a small SaaS does not require enterprise tooling. It requires a script, a schedule, an offsite location, and one uncomfortable afternoon where you deliberately destroy your staging data and bring it back from the dead.
For most indie projects, a nightly logical dump of your database is enough. If you are on Postgres, that means pg_dump piped through gzip. If you are on MySQL, it is mysqldump. These tools are battle-tested, they produce portable files, and they do not care whether you are running on a $5 VPS or a managed platform.
Wrap the dump in a small shell script that does four things: run the dump, compress it, upload it somewhere off your primary server, and delete local copies older than a few days. The offsite part matters more than people think. A backup sitting on the same disk as your database disappears the moment the disk does, and a surprising number of disasters take the whole machine with them.
For offsite storage, object storage like S3, Backblaze B2, or Cloudflare R2 is the natural fit. All three have command line tools you can drop into a cron job. Use a dedicated bucket with credentials scoped only to that bucket, so a leaked key cannot touch anything else. Set a lifecycle rule to expire old backups automatically instead of writing cleanup logic yourself.
Here is the part almost everyone skips. Once a month, or once a quarter if that is honestly all you will do, pick your most recent backup and restore it into a fresh database on a throwaway machine. Then run a couple of queries against it. Check that the row counts look sane. Check that the most recent record is actually recent. If your app has a health check endpoint, point it at the restored database and see what happens.
This drill catches the failures that silently accumulate. Maybe your dump script has been writing zero-byte files for three weeks because the password changed. Maybe your compression step is fine but the upload has been failing due to an expired token, and nobody noticed because cron emails go to an address you never read. Maybe the backup is perfect but your restore procedure is undocumented and you cannot remember which flags you used. All of these are common, and all of them are only visible when you actually try to restore.
Write the restore steps down while you do it. A short markdown file in your repo that says exactly which commands to run, in what order, with what environment variables, is worth more than any dashboard. If you ever have to hand the project to someone else, or if you simply forget after six months, that file is your lifeline.
Finally, alert on backup failure. A cron job that fails silently is worse than no cron job, because it gives you false confidence. Most scheduled task runners can send a webhook on non-zero exit codes. Point that at a Slack channel or an email you actually read. It takes ten minutes and saves you from the worst kind of surprise.