A process restart takes a few seconds and only loses its own memory. A reboot takes the whole edge server down: the tunnel to the hub, the local Kubernetes node, everything the robot depends on it for. Build one pipeline for both and size it for the reboot, and every small app fix now waits on the same caution a reboot needs, for no reason.

This builds on updating robots in the field and the reference architecture. Read those first if you haven’t.

A restart and a reboot are not the same event

The app plane ships through Git. A commit lands, a controller watching the repository pulls the edge server’s state into line with it (ArgoCD is a common choice, but the mechanism matters more than the product), and the affected pod restarts. If the new version is wrong, the revert is another commit, applied the same way. Nothing about this touches the machine the pod runs on, and nothing about it needs to: the kernel, the drivers, and the boot image are exactly what they were a minute ago.

The OS plane ships a signed RAUC bundle to whichever of the edge server’s two slots isn’t currently active (RAUC project docs). Writing the bundle changes nothing yet, the same way downloading an installer doesn’t upgrade anything until you run it. The switch to the new slot only happens inside a maintenance window agreed with the customer, and only after the safe-to-reboot gate confirms the robot isn’t mid-motion and no job is running. Skip that gate, and a reboot can land while an arm is moving. The robot stops mid-task, in front of anyone standing nearby.

Frequency tracks that difference. Application code changes weekly, sometimes daily; a config value, a model weight, a bug fix. The operating system a fleet ships with barely changes at all once it’s stable, and when it does, the change is deliberate and rare enough that the last thing you want is for it to arrive on the same cadence as an app hotfix. Those aren’t two variations on one release process. They’re triggered by different events, gated by different checks, and reversed by different mechanisms.

App planeseconds, reversible by another commitGit commitapp planeControllerapplies itPod restartsedge serverno rebootOKOS planerare, reversible by switching slots backRAUC bundlesignedInactive slotinstalled, not liveMaintenance window+ safe-to-reboot gateboth must clearswitchRebootedge serverNeither path blocks the other. A build can sit in the inactive slot for dayswhile app releases keep shipping through the top path without waiting on it.
blog.bensoussan.de
Figure 1. Two planes, two cadences. The app plane restarts a process on every push; the OS plane only reaches the edge server's inactive slot until a maintenance window and the safe-to-reboot gate both clear, then it reboots.

What goes wrong when they share a pipeline

One pipeline shipping both

Route application releases through the same gate as OS releases, and every push now waits on a maintenance window and a safe-to-reboot check, whether or not it touches anything that requires a reboot. A one-line config fix that used to ship in the time it took the controller to notice the commit now waits for the robot to be idle and the window to open. Article one named speed as the first capability a fleet needs: ship a fix to the whole fleet the day you have it. A shared pipeline trades that away on every release, not only the ones that touch the kernel, because the pipeline can’t tell the two apart once they’re both funneled through it.

It runs the other way too. If app releases are also stuck waiting on a maintenance window, there’s pressure to open that window more often. A check meant for a rare, disruptive event ends up scheduled around the frequent, harmless ones instead.

OS changes delivered as privileged containers

Ship the OS payload as a privileged container instead of a signed image, and the reboot goes away, along with the reason it existed. It can’t work: a kernel version, a driver, or a base image only takes effect at boot, and nothing running inside a container, no matter how privileged, changes what the machine already booted into. Every fleet that ships this at real scale reboots into the new slot instead of faking it live; Android’s own A/B update mechanism works the same way, for the same reason (Android Open Source Project docs). The CRA support obligation from article one is about the kernel and drivers a robot actually boots on, not about what a pod on top of it believes it changed. A privileged container operates one layer above the one that matters, and no amount of privilege closes that gap. The demonstrated way past that gap is a container breaking out to the host entirely, which is a security failure, not an update path (Red Hat Security Advisory, Feb 2019).

Coupling both to a single release train

Keep the two delivery mechanisms separate but version and promote them together, so every release is stamped “app v42 with OS v7,” and the coupling comes back through the back door. An app hotfix now waits for the OS side of that pairing to clear its own review, even though nothing about the fix touches the kernel. Roll back the app half after a bad release, and the tooling either reverts the OS half along with it, which pulls a working kernel back to an older one for no reason, or leaves the pairing inconsistent, which is exactly the drift a fleet-wide version stamp exists to prevent. Either way, the release train has rebuilt the reboot-on-every-release problem one level up, with an extra step in between where nobody notices until a rollback goes wrong. This is the same failure DORA’s research names directly: components that can’t deploy independently force every change through the same coordination and the same blast radius as the slowest one in the bundle (DORA, DevOps Capabilities).

The decision

App and OS releases run on two planes that share nothing beyond the edge server they both land on, and never merge into one train. Each stages through the three rings from article three on its own schedule: a build can be at ring 2 on the app plane, already running on the whole fleet, while the OS plane for that same fleet is still sitting at ring 0 on the test site. Advancing one plane’s ring never requires touching the other’s, and rolling one back never depends on what state the other is in.

That separation is what lets an app fix and a kernel patch behave the way their actual risk differs. One restarts a process and reverts with a commit. The other reboots a machine, waits for a window and a safe-to-reboot check, and reverts by switching a slot back. Merge them into one gate, disguise one as the other inside a privileged container, or tie their promotion to the same release train, and the fast path inherits the slow path’s caution on every single release, which is the opposite of what separating them was supposed to buy.


I’m David Bensoussan. I build deployment and update infrastructure for hardware and robot fleets. If you’re working out how to ship software to machines you can’t reach, book 30 minutes and we can work out your fleet’s update path.