Zero-downtime upgrades (systemd)¶
ShinyHub can be upgraded or restarted without dropping live app sessions or
refusing client connections. On SIGHUP the running control plane re-execs the
new binary, hands off its listening sockets to the successor, drains in-flight
WebSocket sessions, releases the ownership lease, and exits. App processes keep
running throughout (server.shutdown_apps: adopt, the default).
One-time setup¶
- Install the binary at
/usr/local/bin/shinyhub(a real built binary;go runcannot perform graceful reloads). - Set a PID file in
shinyhub.yaml: - Install
deploy/systemd/shinyhub.service, thensystemctl daemon-reloadandsystemctl enable --now shinyhub.
Upgrading¶
# 1. Replace the binary in place with the new version.
install -m 0755 ./shinyhub /usr/local/bin/shinyhub
# 2. Trigger the zero-downtime handoff.
systemctl reload shinyhub
A continuous client sees no connection-refused gap; in-flight sessions drain on
the old process for up to server.drain_timeout (default 60s) before any
straggler is force-closed.
Tunables (shinyhub.yaml)¶
| Key | Default | Meaning |
|---|---|---|
server.pid_file |
(empty) | PID file the ready process writes; required for systemd MAINPID tracking. |
server.upgrade_timeout |
60s |
How long the old process waits for the successor to become ready before aborting the upgrade and continuing to serve. |
server.drain_timeout |
60s |
How long to wait for live WebSocket sessions to close before force-closing them. |
Notes & limits¶
- Failure is safe. If the successor fails to start within
upgrade_timeout, the old process keeps serving - the upgrade simply does not happen. - Database migrations must be backward-compatible and non-blocking. During the handoff window both versions briefly run against the same SQLite database. The successor applies any new migrations at startup while the previous version is still serving on the old schema. An upgrade that adds migrations must therefore use additive / expand-contract changes (the previous version keeps working against the new schema) - never a destructive rename or column drop in the same release - and must avoid long-running/locking migrations (SQLite runs them in a transaction that can block the old process); split large backfills or table rewrites out of the upgrade and run them separately afterward. Same-version restarts apply no migrations and are always safe.
- systemd MAINPID. With
Type=notify, ShinyHub sendsREADY=1plusMAINPID=<own pid>on startup and after each handoff, so systemd retargets the main PID to the successor. The unit setsRestart=on-failure(notRestart=always): a genuine crash is restarted, but the original PID's deliberate exit 0 after a handoff is a clean success, so systemd does not fight it. After your firstsystemctl reload, verify withsystemctl show -p MainPID shinyhubthat it matches the new PID file. - Scope. This is the systemd/VM path and covers all app runtimes (native, Docker, Fargate, remote-worker). Multi-pod Kubernetes rolling upgrades are part of the separate high-availability project.
- Platform. Linux and macOS only (tableflip does not support Windows).