general1214 wordsRead on Arc Codex

Upgrading Ubuntu 25.10 to 26.04 LTS on a Live System with a Custom Kernel and Docker Stack -- The Illusion of Continuity After an Upgrade

### A field report on upgrading a live Ubuntu system without breaking what is already running At 8:00 in the morning, the system appeared stable in the way long-running systems always do: not because they are simple, but because they have learned to behave as if nothing is ever changing beneath them. The machine in question was not a fresh installation or a controlled test environment. It was a live Ubuntu server, already operating as a production node. It ran a custom kernel, maintained multiple Docker services, and depended on a carefully assembled mix of systemd units, Python virtual environments, and third-party repositories that had accreted over time like geological layers. The goal was not to experiment, but to upgrade: to move the system in place from Ubuntu 25.10 to Ubuntu 26.04 LTS while preserving both kernel behavior and running workloads. In theory, this is a package management operation. In practice, it is a negotiation with every assumption the system has ever made about itself. ## Before the upgrade: a system defined by its exceptions Before anything changed, the system was already defined as much by what it avoided as what it used. It ran a custom kernel: ``` 6.18.28-free-mps-free-mps ``` GRUB was explicitly configured to preserve the last saved boot entry rather than defaulting to whatever the distribution preferred. Docker workloads were already active, including services such as Prometheus, Grafana, node-exporter, and multiple application frontends. The system had no swap configured, high memory pressure under container load, and a mixture of official Ubuntu repositories alongside external sources from Caddy, Neo4j, and Google Cloud SDK. This is not an unusual state for modern infrastructure. It is, in fact, typical: a system that has evolved through necessity rather than design, where stability is not imposed but negotiated continuously. ## The upgrade begins: userspace as a moving target The upgrade itself began in the standard way, through a release upgrade process that resolved thousands of packages, installed new system components, and removed older ones that no longer fit the target distribution. Ubuntu 26.04 LTS entered the system not as a clean replacement, but as a layered transformation: * thousands of packages upgraded * new dependencies introduced * legacy packages removed * system libraries replaced in place At a purely procedural level, nothing dramatic occurred. Services were updated, restarted, and reattached. Docker briefly restarted container runtime components, producing transient shim warnings that, in isolation, are routine rather than alarming. But underneath this procedural calm, something more subtle was happening: the system’s internal consistency model was being rewritten while it continued to run. ## The first symptom: Python environments that no longer agree with reality The earliest visible failure was not in the upgrade itself, but in the services that depended on it. A backend service began to fail with a missing module error. Gunicorn, which had been present moments before, was suddenly no longer importable within its execution environment. ``` ModuleNotFoundError: No module named 'gunicorn' ``` This kind of failure is often misinterpreted as missing software. In reality, it is usually missing context. The virtual environment had not “lost” Gunicorn so much as lost alignment with the interpreter and system libraries it believed it was bound to. Reinstalling the dependency restored function, but more importantly, it revealed the underlying condition: the Python environment had drifted as a consequence of the system upgrade, and now required re-synchronization with the new userspace. ## The second symptom: services that start, but do not stay coherent With the backend repaired, systemd began restarting services under the new system conditions. Here, another class of failure appeared. The backend service would start, but fail during initialization or exit prematurely. Gunicorn processes were repeatedly spawned and terminated, not because the code was broken, but because the runtime environment was still settling into its new state. This is one of the less visible consequences of a live OS upgrade: systemd does not wait for conceptual stability. It simply executes unit files in sequence, assuming that “installed” implies “ready.” During this period, the system was technically running, but not yet coherent. ## The third symptom: containerized applications and the problem of localhost The most revealing failure appeared in the frontend layer. A Next.js application running inside Docker attempted to communicate with a Flask backend running on the host system. It did so using the only address it could reasonably assume: ``` 127.0.0.1:5102 ``` Within the container, this was correct. Outside the container, it was not. From the container’s perspective, localhost is self-referential. From the host’s perspective, the backend was alive and listening. But the two were not inhabiting the same network reality. The result was a connection refusal, which surfaced in the browser as a 502 Bad Gateway. This is one of the more misleading errors in web infrastructure. It does not indicate a single failure, but rather the collapse of a chain of assumptions between services that are individually healthy but collectively misaligned. ## The underlying cause: an operating system upgrade that changed everything without changing anything Only when viewed in aggregate does the true structure of the situation become clear. The Ubuntu upgrade did not “break” the system in a direct sense. Instead, it changed the conditions under which the system’s existing assumptions remained valid. * Python environments assumed old interpreter contexts * systemd services assumed stable execution timing * Docker containers assumed network equivalence that no longer held * service startup order became temporarily non-deterministic during restart Each component was correct in isolation. The system was incorrect only in combination. This is the defining characteristic of platform-level upgrades: they do not introduce errors so much as expose dependencies that were previously invisible. ## The recovery: not repair, but re-synchronization Eventually, the system was brought back into alignment not by fixing a single fault, but by restoring coherence across all layers. The backend stabilized under systemd once dependencies were correctly reinstalled. The frontend container was rebuilt and restarted, inheriting a corrected understanding of how to reach the backend services. Docker and systemd re-established their expected runtime boundaries. Redis remained stable throughout. At that point, something notable happened: the system stopped behaving like a collection of services and returned to behaving like a single coordinated stack. ## After the reboot: stability as agreement By 10:30, the system had fully recovered. Ubuntu had been upgraded from 25.10 to 26.04 LTS. The custom kernel remained intact. Docker workloads resumed automatically. Services stabilized under systemd supervision. The frontend and backend once again agreed on how to communicate. Nothing had been rebuilt. Nothing had been redesigned. And yet everything had been re-established. The most important outcome of the entire operation was not the version change itself, but something more subtle: continuity had been preserved through transition. ## Conclusion: what upgrades really test It is tempting to think of an operating system upgrade as a technical procedure with a binary outcome: success or failure. But in practice, it is closer to a stress test of assumptions. The system does not fail because it stops working. It fails because different parts of it continue working under incompatible interpretations of the same world. When those interpretations are brought back into alignment, the system does not feel repaired. It simply feels coherent again. And that coherence is what we recognize, retrospectively, as success.

How it works

Once you click Generate, Ollama reads this article and crafts 5 comprehension questions. Your answers are graded against the article content — general knowledge won't be enough. Score 70+ to count toward your certificate.

Questions are cached — you'll always get the same 5 for this article.