The upgrade is two commands: docker compose pull, then docker compose up -d. Migrations are forward-only, checksummed, lock-serialized, and run before services start; an already-applied migration whose file changed aborts loudly rather than diverging. The management process signs an audit checkpoint every five minutes, so even across an upgrade the record's uncovered tail stays minutes-narrow.
A planned restart should cost zero failed requests: on SIGTERM each process reports not-ready while still serving (drain, default 15 s) so your balancer takes it out of rotation, then closes listeners and lets in-flight work finish (grace, default 20 s). Two prerequisites are easy to miss. The container stop timeout must exceed drain plus grace — Docker's default 10 s would SIGKILL a third of the way into the drain, truncating every request in flight on every docker compose stop; the shipped bundle sets 40 s against the 35 s budget. And your readiness probe must be the one wired to /readyz — a liveness-only probe never notices the drain.
Mid-rollout, the Components board shows old and new versions side by side per instance, so "what runs where" is observed, not assumed.
Now I can upgrade without dropping a single in-flight request.