Your Bot Will Be Restarted — Design for It

·5 min read·Updated
botsreliabilityarchitectureoperationshow-to

Your bot is going to be restarted, repeatedly, for reasons that aren't all under your control — a crash, a redeploy, a configuration change, or platform maintenance. The one constant is that your process gets roughly a second between the stop signal and being killed, which is not enough time to finish anything meaningful. That sounds fragile and isn't: a process designed to be killed at any moment is simpler than one that assumes a graceful goodbye, and the design rules are short. Write state as it changes, make startup idempotent, and never hold something important only in memory.

The four ways your bot restarts

Your process crashed. On a zip deploy a supervisor restarts it about five seconds later and the container never exits — which is why a crash-looping bot still reports as running. With your own image the container exits and Docker's restart policy brings it back. Either way, you come back up.

You redeployed. The container is stopped, removed, and recreated. Everything outside a volume goes with it.

You changed configuration. An environment variable, a port, a volume, or a CPU/RAM preset — all of these recreate the container, which surprises people who think of them as settings rather than deploys.

The platform scheduled one. Apps can be restarted for maintenance. You get a notification in advance — "Application restart scheduled — … will be restarted at {time} UTC" — and often one afterwards confirming it happened, plus entries in the app's event history.

Worth knowing about that last one: if you restart the app yourself before the scheduled window, the scheduled restart is dropped. So a maintenance notice at an awkward time isn't something you have to sit and wait for.

You get about a second

This is the number that shapes everything else. When your process is asked to stop, it has roughly one second before it's killed outright.

That's enough to notice and not enough to act. In practice:

  • A graceful gateway disconnect won't finish. A bot politely closing its Discord connection over five seconds gets cut off after one.
  • Buffered writes are lost. An open transaction, a batched insert, an in-memory queue awaiting flush — assume all of it dies mid-flight.
  • finally blocks and shutdown hooks are best-effort. They run only if the process is still alive to run them, which it may not be.

So don't build a shutdown routine and rely on it. Build so that not running one costs nothing.

Four rules that make restarts boring

1. Write state when it changes, not on shutdown. The single highest-value habit. If a counter matters, persist it when it increments. If a job completed, mark it done immediately. Anything deferred to "cleanup" is anything you're willing to lose.

2. Make startup idempotent. Assume the previous run stopped at the worst possible moment — halfway through a batch, between writing a record and marking it processed. Starting again should be safe, which usually means checking what's already done rather than assuming a clean slate.

3. Keep durable state on a volume. In-memory is gone every restart; the container filesystem is gone every redeploy. Only a volume survives both — and the distinction between those two kinds of restart is exactly what makes this bug feel random.

4. Treat external work as at-least-once. If your bot was posting a message when it died, it may not have posted — or may have posted and died before recording it. Store the intent first, then act, then mark it done, and you can safely retry. The same pattern that keeps webhook receivers honest.

What restarts cost you in practice

  • In-process schedules reset. A job due during the gap doesn't fire; nothing queued it. Fine for a periodic poll where the next run catches up, not fine if a specific run matters — record the last successful run and catch up at startup.
  • Caches go cold. Usually harmless, occasionally a thundering-herd problem if you rebuild everything at once on boot. Lazy-load rather than warming eagerly.
  • Reconnect handlers re-fire. Which is a real hazard if you register event handlers inside them — that's how a bot ends up replying twice, then three times.
  • Startup time is downtime. A redeploy's gap is mostly however long your app takes to become useful, so anything you load at import multiplies the outage.

Testing it is easy

You don't need a chaos engineering practice. Restart your bot at a deliberately awkward moment — mid-command, mid-job — and see what happens. Better still, do it while a background task is running.

If the answer is "a message got half-sent", "a row got written but not marked", or "the counter went backwards", you've found the thing that will eventually bite you at 3am and be much harder to diagnose then.

Do that once on a staging bot, where breaking things costs nothing, and most restart-related bugs surface immediately.

FAQ

How much time does my bot get to shut down?

About a second between the stop signal and being killed. Long graceful-shutdown routines won't complete.

Will I be told before a maintenance restart?

Yes — a notification announcing the scheduled time, and typically one confirming it afterwards. The events tab records both.

Can I avoid a scheduled restart?

Restarting the app yourself before the scheduled window cancels the pending one, so you can take it at a moment that suits you.

Does changing an environment variable restart my bot?

Yes. Env vars, ports, volumes, the run command, and CPU/RAM changes all recreate the container.

What's the difference between a crash-restart and a redeploy?

A crash-restart reuses the same container, so the filesystem survives. A redeploy replaces the container, so anything outside a volume is discarded.

How do I stop losing work when the process dies?

Persist as you go rather than at shutdown, make startup resume safely, and keep anything durable on a volume.

Wrapping up

The instinct when you learn your process can be killed with a second's notice is to try to shut down faster. The better move is to stop needing to. A bot that writes state as it changes and can resume from any point doesn't care whether it stopped because of a crash, a deploy, or maintenance — and that indifference is worth more than any shutdown handler, because it's the only property that holds when the shutdown handler doesn't get to run.