Bot Data Lost on Restart? Put SQLite on a Volume

·5 min read·Updated
discordtelegrambotssqlitevolumeshow-to

If your bot's data is lost on restart, it's because anything your container writes outside a volume lives in the container's writable layer — and that layer is thrown away every time the container is recreated. A bot.db sitting next to your code looks persistent, survives a few restarts, and then silently resets the first time you redeploy. The fix is one step: mount a volume at the directory holding your database, and write there. That takes SQLite from "works until it doesn't" to genuinely durable, and it's usually all a bot needs before you'd consider a real database server.

Why the file disappears

A container image is read-only. Everything your process writes at runtime goes into a thin writable layer stacked on top, which belongs to that container instance, not to your app. Recreate the container and you get a fresh layer from the same image — your code is identical, and your data is gone.

This is standard container behaviour, not a platform quirk (Docker docs), and it catches bot authors constantly because a bot's database starts tiny and gets written rarely. A Discord bot storing per-server prefixes in SQLite is the canonical example — it works in testing and resets in production (SQLite forum).

The confusing part is the timing. The data doesn't vanish when the process restarts; it vanishes when the container is replaced. Those look identical from the outside, which is why it feels random.

What actually triggers a reset

On JustRunMy.App, containers are always recreated from the image rather than resumed, so the writable layer is discarded whenever any of these happen:

  • You redeploy — a git push, a Docker push, or a new zip build.
  • You change the image the app points at.
  • You change environment variables, config files, ports, or volumes.
  • You change the CPU or RAM quota.
  • The app is moved or rebuilt by the platform.

A crash-restart is the exception: when your process dies and is restarted inside the same container, the layer survives. That's the worst possible failure mode for learning the lesson, because your bot can crash and recover with its data intact for weeks, then lose everything the first time you push a fix.

Treat the writable layer as scratch space. If you'd be upset to lose it, it belongs on a volume.

Mount a volume at the directory, not the file

In the app's Volumes panel, add a volume with a container path like /data. Then point your bot at a file inside it:

# not ./bot.db  — that's the writable layer
DB_PATH = "/data/bot.db"
// node
const db = new Database('/data/bot.db');

Two things to get right:

Mount a directory, not the database file. SQLite doesn't just write bot.db — it creates bot.db-wal and bot.db-shm alongside it in WAL mode, and journal files in others. If you mount only the single file, those siblings land back on the writable layer and you get a corrupted database rather than a missing one, which is worse. Give it a directory and let SQLite own the whole thing.

Adding a volume restarts the app. The mount is applied when the container is created, so saving the volume triggers a restart. Do it before you have data you care about — a volume added later starts empty, and the old file is in the layer you're about to discard. If you already have data in there, copy it out through the app's shell first; shell access is included free on every app.

Volumes are real Docker volumes on the node's local disk, which also means SQLite behaves properly — its locking assumes a local filesystem and misbehaves on network storage.

How much room you get

Volume space counts against your disk quota, visible on the Limits page.

The Limits page in the JustRunMy.App panel showing disk quota alongside CPU, RAM, apps, ports, and Runny AI build limits

A 0.3 GB volume can hold a substantial SQLite database of bot settings, warnings, or economy balances. Disk costs $0.25 per GB per month, billed per minute; 0.3 GB costs $0.075 for a full month (7.5 cents).

Your app can read its mounted volume paths at runtime from the DF_VOLUMES environment variable, which is handy if you'd rather not hard-code /data in two places.

When SQLite isn't enough

SQLite is the right default for a bot. It's a single file, no server, no connection string, no second app to keep running. Reach for Postgres when you hit one of these:

  • More than one process writing. SQLite allows one writer at a time. If you split your bot into a worker plus a dashboard, both writing, you'll meet database is locked.
  • You want the data to outlive the app. A volume is attached to one app; a separate database app can be rebuilt independently.
  • You need real concurrency or the query surface — proper types, indexes under write load, multiple readers hammering it.

At that point there's a one-click Postgres template, which is the same idea one level up: a container with a volume, where the volume matters for exactly the same reason.

Back it up anyway

A volume protects you from redeploys, not from mistakes. Nothing backs it up for you. For SQLite, the correct copy is a real backup rather than cp on a live file:

sqlite3 /data/bot.db ".backup '/data/bot-backup.db'"

Run that from the app shell or on a schedule inside your bot, and pull the result somewhere else. A dump sitting in the same volume protects you from corruption, not from losing the volume.

FAQ

Why does my Discord bot's database reset?

It's stored in the container's writable layer, which is discarded whenever the container is recreated — on redeploy, image change, or config change. Move the file onto a mounted volume.

Does restarting my bot delete its data?

A process crash-restart inside the same container keeps the data. A redeploy or any config change recreates the container and discards it. Both look like "a restart," which is why this is confusing.

Where should I mount the volume?

On a directory such as /data, then write /data/bot.db. Don't mount the database file itself — SQLite's WAL and journal siblings need to live in the same mounted directory.

Is SQLite safe to use here?

Yes, on a volume. Volumes are local disk, which is what SQLite's file locking expects. The main limit is one writer at a time.

Will a volume survive a redeploy?

Yes — that's the point. Volumes are separate from the container and are remounted each time it's recreated.

How much disk does a bot database need?

Almost none. A 0.3 GB volume holds a very large amount of SQLite; disk is also the cheapest quota to increase.

Wrapping up

"My bot's database keeps resetting" is nearly always the same bug, and it isn't in your code — it's a file written to a filesystem designed to be thrown away. One volume, one absolute path, and the problem is permanently gone. Do it on day one rather than after the push that eats your data, because the failure mode is specifically designed to look fine right up until it doesn't.