TL;DR: When a config file changes, the usual way to pick it up is to restart the process. For a long-running service that restart is expensive: dropped connections, lost in-flight work, a cold start. go-tool-base and rust-tool-base both support hot-reload instead. The process watches its config files, re-reads them and swaps the new values in atomically, all while still running. The case where this really pays off is a Kubernetes pod, where a ConfigMap or Secret mounted as a volume is rewritten in place and a hot-reload picks it up with no pod restart at all.
The default answer is a restart
Configuration lives in a file. The file changes: someone edits a setting, rotates a credential, flips a feature flag. How does the running process find out?
Overwhelmingly, the honest answer is that it doesn’t. A process reads its config once, at startup, and that snapshot is frozen for the life of the process. Change the file and nothing happens until you restart, at which point a fresh process reads the fresh file.
For a short-lived CLI invocation that’s completely fine. It reads config, does its job, exits, and the next invocation reads whatever the file says then. But the same frameworks are also used to build long-running services, and for a service “just restart it” is not the small thing it sounds like.
What a restart actually costs
Restarting a long-running service means every open connection drops. Any in-flight request is lost or has to be retried by whoever sent it. Caches that took real time to warm are cold again. There’s a window, short but real, where the service simply isn’t serving.
If the thing you changed was a log level, or a feature flag, or a timeout, you’ve paid a disruption wildly out of proportion to the change. And the calculation only gets worse as the service gets more important, because the services you least want to bounce on a whim are exactly the ones that matter most.
Hot-reload: re-read in place
Hot-reload is the alternative, and both go-tool-base and rust-tool-base support it.
The process doesn’t read config once and freeze it. It watches the config file. When the file changes, it re-reads it, re-applies it, and carries on running. No new process, no dropped connections, no cold start. The change lands in the live process.
The shape is the same in both frameworks:
- A file watcher notices the config file changed. Underneath, this is the operating system’s own file-notification facility,
inotifyon Linux and its equivalents elsewhere. rust-tool-base reaches it through thenotifycrate; go-tool-base, through the watcher built into Viper. - A debounce step waits for the writes to settle. Saving a file is often several separate operations, and you don’t want to reload three times for one edit.
- The config is re-parsed from disk.
- The new config is swapped in atomically.
- Observers are notified, so the subsystems that care can react.
Steps four and five are the ones worth slowing down on, because they’re where a naive hot-reload goes wrong.
The two details that make it safe
The atomic swap. You do not mutate the live config object in place. A reader on another thread, partway through reading it, would see a torn mix of old and new values, and that is a genuinely nasty class of bug. Instead the process builds a new, complete config value and swaps the pointer to it in a single atomic operation. Any reader sees either the entire old config or the entire new one, never a blend. rust-tool-base does this with arc-swap; go-tool-base does the equivalent. Reads stay cheap and lock-free, and an update is one pointer swap.
The observer notification. Re-reading the file isn’t the end of the job. Some subsystems have to do something when config changes: a connection pool resizes, a logger changes level, a rate limiter takes a new ceiling. So a hot-reload system has to let those subsystems subscribe. rust-tool-base hands observers a watch::Receiver, a channel that always holds the latest value; go-tool-base exposes an Observable interface. A subsystem subscribes once and reacts every time config changes, for the life of the process.
Where this earns its keep: a Kubernetes pod
Hot-reload is a nicety on a developer’s laptop. Inside a Kubernetes pod it becomes genuinely valuable, and the reason is a neat fit between how Kubernetes delivers config and how a file watcher works.
In Kubernetes you don’t usually bake configuration into the container image. It lives in ConfigMap and Secret objects, and the clean way to consume them is to mount them as volumes. Mount a ConfigMap as a volume and each key becomes a file in the pod’s filesystem.
Here’s the part that connects to everything above. When you update that ConfigMap or Secret, Kubernetes does not restart your pod. The kubelet notices the object changed and rewrites the projected files inside the still-running pod. The files on disk change underneath a process that never stopped.
That file rewrite is exactly the event a hot-reload watcher exists to catch. So the whole chain becomes:
- You
kubectl applyan updated ConfigMap, or rotate a Secret. - The kubelet updates the projected files inside the pod.
- The framework’s file watcher sees the write.
- The config is re-parsed, swapped in atomically, and observers are notified.
- The new configuration is live, and the pod never cycled.
You’ve changed a running service, in a running pod, with no rollout, nothing terminated and recreated, no dropped traffic. Rotate a database credential, raise a log level to debug an incident in progress, flip a feature flag: all of it live. For a service where a restart is the thing you’re trying hard to avoid, the kind of long-running service these frameworks are built for, that’s the difference between a config change being routine and being an event.
The honest caveats
Two things, so this doesn’t read as magic.
First, not everything can be hot-reloaded. Some configuration genuinely needs a restart: the port a server binds to, the size of a thread pool, anything wired up exactly once at process start. Hot-reload covers the large category of settings a subsystem can re-read and re-apply; it does not abolish restarts. A config system worth its salt is clear about which settings are live and which are not.
Second, a Kubernetes gotcha that catches people out. The in-place file update happens for ConfigMaps and Secrets mounted as volumes. Consume the same ConfigMap as environment variables instead, and those are fixed when the container starts and never update, short of a restart. If you want hot-reload in a pod, mount config and secrets as files, not env vars. And even with volumes the update isn’t instant: the kubelet syncs on a period, around a minute by default, so a reload is “within a minute or so,” not “the moment you hit apply.”
The short version
A config file changes, and the default way to pick it up is to restart the process. For a long-running service that restart costs dropped connections, lost work and a cold start, often for a change as small as a log level.
go-tool-base and rust-tool-base both support hot-reload instead: a file watcher catches the change, the config is re-parsed and swapped in atomically so no reader sees torn state, and observers are notified so subsystems can react, all in a live process. The setting where it pays off most is a Kubernetes pod, where ConfigMaps and Secrets mounted as volumes are rewritten in place by the kubelet and the watcher catches that write directly. Mount them as volumes rather than env vars, allow for the kubelet’s sync delay, accept that some settings still need a restart, and within those limits “the config changed” stops meaning “cycle the pod.”