Minimum viable backup strategy for solo founders
作者:Seaworthy · 2026年6月12日
Photo by T. Selin Erkan on Unsplash
The thing managed databases do not tell you
You are paying for Supabase or PlanetScale or Turso, and somewhere in the docs it mentions "automatic backups." You feel covered. You move on.
Here is what that sentence actually means: the provider protects you from their infrastructure failing. A disk dies, a region goes dark, their deployment breaks something at the storage layer. They handle it. That is genuinely useful.
What it does not protect you from is you. Dropping a table, running a migration that corrupts data, deleting rows with a WHERE clause that was slightly wrong. Application bugs that silently zero out a column over three weeks. A disgruntled contractor. These are the real failure modes for a solo founder, and most managed database backup systems are designed around infrastructure failures, not human ones.
Check your provider's documentation carefully. How long is the retention window? Some free and lower tiers keep 24 hours. Some keep 7 days. What is the granularity, daily snapshots or point-in-time recovery? Can you actually trigger a restore yourself, or do you file a support ticket and wait?
If you do not know the answers off the top of your head, that is a problem.
RPO and RTO without the textbook definitions
Two numbers matter when you think about backups.
RPO is how much data you can afford to lose. If you take a snapshot every 24 hours and your database gets corrupted at hour 23, you lose 23 hours of data. That is your RPO: 24 hours in the worst case. For a transactional app with paying customers, that is probably catastrophic. For a blog, maybe fine.
RTO is how long it takes to get back up after a failure. Restoring a 50 GB Postgres dump takes time. Spinning up a new instance, restoring, updating DNS, flushing caches: you can easily spend 4 to 6 hours on a restore you have never practiced. That is your RTO.
Decide what numbers you can actually live with, then check whether your current setup delivers them. Most people have never thought about either number explicitly.
Setting up snapshots you actually control
Do not rely solely on whatever your provider does automatically. Add a second layer that you own and can test.
For a Postgres database, pg_dump piped to an object store (S3, R2, Backblaze B2) on a cron schedule is about 20 lines of shell. Here is a basic version:
#!/bin/bash
set -euo pipefail
TIMESTAMP=$(date +%Y%m%dT%H%M%S)
FILENAME="backup-${TIMESTAMP}.dump"
S3_BUCKET="s3://your-bucket/db-backups/"
pg_dump \
--format=custom \
--no-acl \
--no-owner \
"$DATABASE_URL" \
| aws s3 cp - "${S3_BUCKET}${FILENAME}"
echo "Backup complete: ${FILENAME}"
Run this from a cron job on a small VM, a GitHub Actions scheduled workflow, or a Fly.io machine. Set a lifecycle rule on the bucket so objects older than 30 days are deleted automatically: you do not want to pay for backups forever and you do not want to manage cleanup manually.
The --format=custom flag produces a binary format that is faster to restore than plain SQL and supports selective table restores, which matters when you only corrupted one table.
Encrypt at rest. If you are using S3 or R2 with a private bucket and IAM, you get encryption in transit. Add server-side encryption on the bucket itself. If you want to be paranoid, pipe through age or gpg before uploading and store the key somewhere separate.
The restore test most people skip
Taking backups without testing them is a comforting fiction. The backup might be corrupt. The restore process might require a version of pg_restore you do not have. The credentials might be wrong. You will not know until you need it.
Run a restore test once a month. It does not need to be elaborate. Spin up a local Postgres container, pull the latest backup from your bucket, restore it, and run a quick sanity check:
# Pull and restore
aws s3 cp s3://your-bucket/db-backups/backup-latest.dump /tmp/backup.dump
docker run -d \
--name test-restore \
-e POSTGRES_PASSWORD=test \
-p 5433:5432 \
postgres:16
pg_restore \
--host=localhost \
--port=5433 \
--username=postgres \
--dbname=postgres \
/tmp/backup.dump
# Basic sanity check
psql \
"postgresql://postgres:test@localhost:5433/postgres" \
-c "SELECT COUNT(*) FROM users;"
If the count is roughly what you expect, you are good. Tear it down and move on. The whole thing takes 15 minutes once you have done it once.
Schedule this. Put it in your calendar. Without a schedule it will not happen.
What to monitor
Once you have backups running, add a dead man's switch. Your backup script should send a heartbeat to a monitoring service like Healthchecks.io every time it completes successfully. If 25 hours pass without a heartbeat, you get an alert. This catches failed cron jobs, expired credentials, and full disks before you find out the hard way.
You also want to monitor backup size. A backup that is suddenly 90% smaller than yesterday means something went wrong with your data, not the backup process.
Realistic targets for a solo app
A 6-hour snapshot interval (4 backups per day) with 30-day retention is achievable for almost any solo SaaS at near-zero cost. That gives you an RPO of 6 hours in the worst case. Your RTO depends on how often you practice: if you have restored from backup twice this year, you can probably do it in under an hour. If you have never done it, plan for half a day.
That is not a guarantee. It is a floor. And a floor beats nothing, which is what most solo apps actually have.
Seaworthy flags missing backup configuration and untested restore paths as part of its resilience checks.
本文由 AI 生成,总结了公开可用的来源。
Seaworthy 会扫描你的代码仓库,发现类似本文提到的各种问题。
安全漏洞、泄露的密钥和配置错误——在部署前捕获。免费运行,无需账户。