Nothing Backs Up Your Volumes — Here's How to Do It Yourself

·5 min read·Updated
backupsvolumespostgressqlitedatabaseshow-to

There are no automatic backups on JustRunMy.App. A volume keeps your data across redeploys and restarts — that's what it's for — but it protects you from the platform replacing your container, not from a bad migration, a buggy delete, a corrupted database file, or an app you remove by accident. If losing the data would ruin your week, you need your own routine: take a consistent dump, get it off the platform, and test restoring it. That's three steps, it's maybe thirty lines, and it's the difference between an incident and an inconvenience.

What a volume actually protects against

Worth being precise, because "persistent storage" gets read as "safe":

Failure Volume saves you?
Redeploy / container recreated Yes — this is the point
App crash and restart Yes
Bad migration, accidental DELETE No
Corrupted database file No
You delete the app No
Ransomware on an exposed database No

Everything in the bottom half is a data problem, and a volume is storage, not history. Only a backup gives you a point to go back to.

Take a consistent dump, not a file copy

The most common mistake is copying a live database file. A database being written to is not in a consistent state on disk, so cp app.db backup.db on a busy SQLite database can produce a file that restores into an error.

Use the database's own dump tool, which knows how to produce something coherent:

# Postgres
pg_dump --no-owner --format=custom "$DATABASE_URL" > /data/backups/db-$(date +%F).dump

# SQLite — not cp
sqlite3 /data/app.db ".backup '/data/backups/app-$(date +%F).db'"

sqlite3 .backup uses the backup API, which takes a proper snapshot of a live database. It's the one-word difference that decides whether your backup is restorable.

For uploaded files or anything that isn't a database, a tar of the directory is fine — files don't have the same consistency problem, though you should avoid archiving something mid-write if you can.

Get it off the platform

This is the step people skip, and it's the one that matters.

A dump written into the same volume protects you from a bad migration. It does not protect you from losing the volume, deleting the app, or losing access to the account — and those are exactly the scenarios where you most want a backup.

So ship it somewhere else:

  • Object storage (S3, R2, B2) is the standard answer — cheap, durable, and every language has a client. Push the dump after you take it.
  • A private git repo works for genuinely small data. Not elegant, but versioned and off-site.
  • Another machine you control, over SFTP or similar.

Whichever you pick, the credential goes in an environment variable — and give it write-only access to the backup bucket if your provider supports that, so a compromised app can't delete your history.

The disk trap that stops all your apps

Here's the failure mode specific to this platform, and it's a nasty one because it's caused by doing backups.

If your total disk usage exceeds the account limit, every application in the account is stopped and can't be restarted until the total drops back below it. That's account-wide, not per-app.

A daily backup written into a volume and never deleted is a slow-motion version of exactly that. Six months of dumps quietly grows until it trips the limit, and then your bot, your API, and your database all stop together — because of the safety measure.

Two rules avoid it entirely:

  • Prune as you go. Keep the last 7 daily and maybe 4 weekly copies locally; the long tail lives in object storage where it isn't competing with your apps for quota.
  • Delete the local copy after upload if the data is large. The volume is a staging area, not an archive.

Check the disk gauge on the Limits page occasionally — it's the one quota with an account-wide failure mode.

Automate it in-process

There's no cron service, so the schedule lives inside a long-running app — the same pattern as any other scheduled task:

scheduler.add_job(backup, "cron", hour=3, minute=17, max_instances=1)

Three details worth getting right:

  • Pick an odd minute. Everyone schedules 03:00; you gain nothing by joining them.
  • max_instances=1. A backup that overruns shouldn't start a second copy alongside itself.
  • Log the outcome, including success. A backup job that silently stopped running six weeks ago is indistinguishable from one that's working, unless you left yourself a line to check.

Remember that a redeploy resets an in-process schedule, so a job due during the gap doesn't fire. For daily backups that's fine; just don't build a system where a single missed run matters.

Test the restore

An untested backup is a belief, not a backup. The failure you're guarding against is the one time you actually need it, which is a terrible moment to discover the dump is empty, truncated, or from the wrong database.

Restore into a staging app and check it works — a second app costs only the resources it uses, so this is cheap. Do it once when you set the routine up, and again after any change to your schema or your backup script.

While you're there, it doubles as a migration rehearsal: real-shaped data is the only way to know a migration behaves.

FAQ

Does JustRunMy.App back up my data automatically?

No. There are no automatic backups or volume snapshots. Volumes persist across redeploys, but nothing keeps historical copies.

Isn't a volume enough?

Only against container replacement. A volume doesn't help with a bad migration, an accidental delete, a corrupted file, or deleting the app.

How do I back up SQLite safely?

Use sqlite3 db ".backup 'target.db'", not cp. Copying a live database file can produce a torn, unrestorable copy.

Where should backups be stored?

Off the platform — object storage such as S3, R2, or B2. A dump in the same volume doesn't survive losing the volume.

Can backups break my apps?

Yes, indirectly. If accumulated backups push total disk over the account limit, every app in the account stops until you free space. Prune local copies.

How often should I back up?

Match it to how much work you're willing to redo. Daily is right for most small projects; before every migration is the other moment that matters.

Wrapping up

Backups are the least interesting thing to build and the only one that matters on the day it matters. The routine is short: dump with the database's own tool, push it somewhere that isn't here, delete the local copy so it doesn't eat your quota, and restore it once so you know it works. Do it when you set the app up, rather than promising yourself you will after the first scare.