Overview
In June 2026, a Docker Desktop update on SPECULAR-CORE, running alongside Proton VPN's always-on kill switch, brought down the entire local AI stack simultaneously. Open WebUI lost its data volume. Ollama stopped accepting connections from inside containers. ComfyUI refused to import its dependencies. The monitoring stack reported every service as down.
The immediate instinct was to patch each failure in isolation. The correct move turned out to be more fundamental: decommission Docker Desktop for Windows, install a native Linux Docker engine inside WSL2, and remove Docker Desktop's Windows and Hyper-V bridge from the recovered container-to-host path.
This case study documents what broke, why it broke, and the exact sequence of decisions that rebuilt the stack into something structurally more resilient than what existed before.
This article records the June 2026 recovery topology. It does not describe every service later added to SPECULAR-CORE, and it does not claim that native WSL2 Docker removes every possible VPN, kernel, bridge, or host-network failure. It records the narrower result proved here: the Docker Desktop and Windows Hyper-V path was removed from this stack.
Original Architecture
The stack before the failure ran across two distinct execution layers with Docker Desktop for Windows acting as the bridge between them.
Ollama and ComfyUI ran natively inside WSL2 Ubuntu, serving their APIs on localhost ports. Docker Desktop for Windows managed the frontend containers — Open WebUI, Uptime Kuma, and Portainer — using its own Hyper-V virtual network switches to route traffic between the Windows host, the Docker bridge network, and the WSL2 subsystem.
| Service | Layer | Port |
|---|---|---|
| Ollama | Native WSL2 | 11434 |
| ComfyUI | Native Python venv | 8008 |
| Open WebUI | Docker Desktop container | 3000 |
| Uptime Kuma | Docker Desktop container | 3001 |
| Portainer | Docker Desktop container | 9000 |
This worked until it didn't. The architecture required traffic to traverse three distinct network namespaces on every request: the Docker bridge, the Hyper-V virtual switch, and the WSL2 subsystem. Each crossing was a potential failure point.
The original architecture was functional but fragile. Three network namespace crossings per request means three places where a firewall policy, a kernel update, or a VPN routing change can silently break everything at once.
The Failure Chain
Two things happened within the same maintenance window. Docker Desktop updated itself, and Proton VPN's always-on kill switch was active.
The update changed how Docker Desktop managed its internal Hyper-V network switches. Proton VPN's kernel-level firewall, running in always-on mode, flagged the resulting cross-subsystem traffic as an unencrypted local data leak and dropped it. Traffic traversing from the Docker bridge through the Windows host OS to the WSL2 backend was systematically blocked.
The result was a cascade. Every container that needed to reach Ollama got ECONNREFUSED. Open WebUI's WebSocket connections dropped with xhr poll error. The volume mount that held the user database got swapped to an empty named volume during the Docker update, wiping the active session. Every monitor in Uptime Kuma flipped red simultaneously.
From the outside it looked like total system failure. From the inside, each service had a distinct root cause.
Phase I — Hyper-V Network Collapse
Every container that attempted to reach Ollama on host.docker.internal:11434 returned a connection refused error. Switching to 172.17.0.1, 172.23.x.x, and localhost all produced the same result. The Ollama service was confirmed running and listening on 0.0.0.0:11434 via ss -tlnp inside WSL2.
Proton VPN's kernel-level firewall was dropping all traffic crossing the Windows Hyper-V virtual switch between the Docker bridge network and the WSL2 network namespace. The packets left the container correctly but were intercepted before reaching the WSL2 subsystem. No Windows firewall rule, no Docker network flag, and no host.docker.internal alias could bypass a VPN kill switch operating at the kernel level.
Attempting to work around this with .wslconfig mirrored networking mode broke Docker Desktop's WSL2 integration entirely, producing Wsl/Service/0x8007274c errors and taking the Docker daemon offline. That was reverted immediately.
The structural fix was to remove Docker Desktop's Windows-managed bridge from this path. Installing a native Linux Docker engine inside WSL2 moved container networking onto the Linux host boundary; in the recovery tests, container-to-host traffic no longer crossed the Hyper-V hop that Proton VPN had blocked.
sudo apt-get update && sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --yes --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
In the recovered configuration, 172.17.0.1 was the validated host-facing Docker bridge address for container-to-host traffic. It remained stable across the validation reboots without being presented as a universal or permanent address for every future Docker configuration.
Native Linux Docker Engine installed inside WSL2. The recovered container-to-host path stays inside the Linux host boundary, and this build used 172.17.0.1 as its validated Docker bridge gateway instead of host.docker.internal.
Phase II — Volume Isolation and Data Loss
After the networking fix, Open WebUI launched but presented a blank sign-up screen. All bots, knowledge bases, and user accounts were gone. The container was running but treating itself as a fresh installation.
The Docker Desktop update had swapped the volume mount from the explicit L drive path (/mnt/l/Openweb:/app/backend/data) to an anonymous named volume, silently isolating the container from the database file on the L drive. Additionally, migrating to native Linux Docker running as root caused an immediate permission collision with the existing ext4 data directory, causing the backend to crash in a silent restart loop rather than report the error clearly.
Two fixes were required. First, the file ownership on the data directory was realigned to the root Docker process context:
sudo chown -R root:root /home/atlas/openwebui-core-data
sudo chmod -R 775 /home/atlas/openwebui-core-data
Second, the container was redeployed with an explicit volume binding to the native Linux filesystem path rather than the NTFS-mounted L drive path. Open WebUI's database performs frequent write operations; on NTFS those writes produce locking conflicts under a root Docker daemon:
sudo docker run -d \
-p 3000:8080 \
-v /home/atlas/openwebui-core-data:/app/backend/data \
-e OLLAMA_BASE_URL=http://172.17.0.1:11434 \
--name open-webui \
--restart always \
ghcr.io/open-webui/open-webui:main
The user database was restored from the automated weekly backup at /mnt/l/Backups/openwebui-backup-20260601/webui.db. All bots, system prompts, knowledge bases, and RAG configurations were recovered intact.
Open WebUI data directory migrated from NTFS L drive mount to native Linux filesystem at /home/atlas/openwebui-core-data. File ownership realigned to root Docker context. Database restored from weekly backup. All ten workbots recovered.
Phase III — ComfyUI venv Corruption
ComfyUI's Python environment, which had been created inside the ComfyUI directory on the NTFS-mounted L drive, stopped resolving installed packages. pip list reported packages as installed; python -c "import torch" returned ModuleNotFoundError. The venv was broken at the symlink level.
NTFS does not fully support the Linux symlink structure that Python virtual environments depend on. When the native Linux Docker daemon took over as root, the permission model for the L drive mount changed, invalidating the symlinks inside the venv. Packages were present on disk but the interpreter could no longer follow the symlink chain to find them.
The fix is an architectural principle rather than a patch: Python environments belong on the native Linux filesystem. Model files, code, and data can live on NTFS; the interpreter environment must not.
# Remove the broken venv from NTFS
rm -rf /mnt/l/StableDiffusion/ComfyUI/venv
# Create a new venv on the native Linux filesystem
python3.11 -m venv ~/comfyui-venv
source ~/comfyui-venv/bin/activate
# Reinstall dependencies using the absolute venv pip path
/home/atlas/comfyui-venv/bin/pip install torch torchvision torchaudio \
--index-url https://download.pytorch.org/whl/cu128
/home/atlas/comfyui-venv/bin/pip install -r /mnt/l/StableDiffusion/ComfyUI/requirements.txt
The launch script was updated to call the venv Python by absolute path, bypassing any activation ambiguity:
#!/bin/bash
export PATH="/home/atlas/comfyui-venv/bin:$PATH"
cd /mnt/l/StableDiffusion/ComfyUI
/home/atlas/comfyui-venv/bin/python main.py \
--listen 0.0.0.0 \
--port 8008 \
--output-directory /mnt/l/StableDiffusion/Output \
--cuda-device 0 \
--disable-cuda-malloc
All three models (Flux.1 Dev fp8, DreamShaper XL, Deliberate v6) remained untouched on the L drive throughout. No re-downloading was required.
venv relocated from NTFS L drive to native Linux filesystem at ~/comfyui-venv. Launch script updated to use absolute Python path. All 25GB of models recovered without re-download.
Phase IV — Ollama Bridge Routing
With Open WebUI running under native Linux Docker, the Ollama connection URL needed to be updated. host.docker.internal no longer resolved correctly without Docker Desktop's DNS injection layer. localhost and 127.0.0.1 refer to the container's own loopback, not the host. The WSL2 eth0 IP (172.23.x.x) works but changes on every WSL2 restart.
host.docker.internal is a Docker Desktop for Windows feature. Native Linux Docker does not inject this hostname. Without it, containers have no built-in mechanism to resolve the host machine's address. The WSL2 subsystem's eth0 IP is dynamically assigned by the Hyper-V NAT stack and changes across restarts.
The solution used in this recovery was the native Linux Docker bridge gateway: 172.17.0.1. It is the host-facing interface of the docker0 bridge and, in the validated configuration, remained stable across the reboot tests while avoiding dependence on the changing WSL2 eth0 address.
The Ollama systemd override was updated to broadcast on all interfaces and point at the correct model storage path:
echo -e "[Service]\nEnvironment=\"OLLAMA_HOST=0.0.0.0\"\nEnvironment=\"OLLAMA_MODELS=/mnt/l/Ollama/models\"" \
| sudo tee /etc/systemd/system/ollama.service.d/override.conf
sudo systemctl daemon-reload && sudo systemctl restart ollama
Open WebUI's Ollama connection URL was set to http://172.17.0.1:11434. Uptime Kuma monitor URLs were updated to http://172.17.0.1:[port] for each service.
In the recovered configuration, container-to-host service references used 172.17.0.1, required no hostname resolution, and reached Ollama from each tested container on port 11434.
New Architecture
After the migration, all five services operated within one Linux host boundary. Docker still used separate container network namespaces behind its Linux bridge, but container-to-host traffic no longer crossed Docker Desktop's Hyper-V layer or the Windows host network path. The recovered routing was simpler and easier to inspect without claiming that Windows could never affect the machine in another way.
| Service | Layer | Launch Method | Internal Target | External Port |
|---|---|---|---|---|
| Ollama | Native WSL2 | systemd | 0.0.0.0:11434 | localhost:11434 |
| ComfyUI | Native Python venv | launch_comfyui.sh | 0.0.0.0:8008 | localhost:8008 |
| Open WebUI | Native Linux Docker | docker run | http://172.17.0.1:11434 | localhost:3000 |
| Portainer | Native Linux Docker | docker run | /var/run/docker.sock | localhost:9000 |
| Uptime Kuma | Native Linux Docker | docker run | 172.17.0.1:[port] | localhost:3001 |
The Windows startup sequence (ATLAS_BOOTSTRAP.bat) was updated to start the native Linux Docker service via WSL rather than waiting for Docker Desktop:
wsl -d Ubuntu -u root bash -c "service docker start"
Docker Desktop for Windows is disabled on startup and no longer part of the boot sequence.
In the June 2026 recovery configuration, Docker Desktop remained disabled because running it beside the native engine reintroduced port and routing conflicts. Container-to-host lookups in this recorded topology used the validated Linux bridge address rather than a Docker Desktop hostname.
Outcomes
The immediate outcome was a working system. The more significant outcome was removal of the specific Windows-managed container path implicated in the collapse. That narrowed the failure surface for this topology, but it did not make the wider machine immune to every VPN, kernel, bridge, or Windows networking change.
The transferable principle is this: when a compound failure exposes a structural dependency you didn't know existed, the correct response is not to patch around it but to remove it. The Docker Desktop bridge between Windows and WSL2 was convenient until it became a single point of failure for everything. Replacing it with something simpler — a native Linux engine and a validated bridge path — cost an afternoon and removed that known failure path from the recovered stack.