Devcontainer – Supervisor Patches¶
This document records three bugs that prevent miniEMS (and any local add-on) from running correctly inside the Home Assistant devcontainer (ghcr.io/home-assistant/devcontainer:2-addons). All three stem from the same root cause: the supervisor assumes it runs on Home Assistant OS (HAOS), not inside a Docker-in-Docker container without systemd.
Each section explains the symptom, root cause, and the patch applied. All patches are applied automatically on every container start via postStartCommand in .devcontainer.json.
1. HA Core fails to start (/run/supervisor missing)¶
Symptom
ERROR [supervisor.docker.manager] Can't create container from
ghcr.io/home-assistant/qemux86-64-homeassistant:...:
[400] invalid mount config for type "bind":
bind source path does not exist: /run/supervisor
Root cause
The supervisor bind-mounts /run/supervisor from the host into the HA Core container so that Core can expose its Unix socket there. In HAOS this directory is created by the OS init process. In the devcontainer it never exists.
Fix
Create the directory before supervisor_run is called:
Added to the front of postStartCommand in .devcontainer.json.
2. Add-on installation blocked (docker_gateway_unprotected)¶
Symptom
WARNING [supervisor.jobs] 'AddonManager.install' blocked from execution,
system is not healthy - docker_gateway_unprotected
Root cause
Supervisor PR home-assistant/supervisor#6650 introduced a gateway firewall check. It applies iptables rules via a systemd transient unit (D-Bus call to org.freedesktop.systemd1). In the devcontainer, systemd is not running — only the D-Bus daemon is. The D-Bus call fails, the supervisor marks the system unhealthy, and all add-on operations are blocked.
The check is in supervisor/host/firewall.py, method apply_gateway_firewall_rules.
Fix — supervisor_firewall_patch.py
Skip the entire gateway firewall check when SUPERVISOR_DEV=1 (the env var already set in every devcontainer run):
async def apply_gateway_firewall_rules(self) -> None:
if self.sys_dev: # <-- added
_LOGGER.info("Skipping gateway firewall rules in developer mode (SUPERVISOR_DEV=1)")
return
...
self.sys_dev is the CoreSysAttributes.sys_dev shortcut, which reads os.environ.get("SUPERVISOR_DEV") == "1".
Note:
self.sys_core.dev(theCoresub-object) does not have this property; useself.sys_devorself.coresys.dev.
The patched file is stored in the repo as supervisor_firewall_patch.py and copied into the supervisor container on every start.
3. REST calls to HA Core return 502 (/run/os/core.sock missing)¶
Symptom
WARNING ha_ws_client – REST error [502]: Bad Gateway
DEBUG [supervisor.homeassistant.api] Error on call http://localhost/api/core/state:
Cannot connect to unix socket /run/os/core.sock ssl:False [No such file or directory]
Root cause
Modern HA Core versions (≥ 2024.11) support a Unix socket transport for supervisor–core communication. When supports_unix_socket is True, the supervisor:
- Sets
SUPERVISOR_CORE_API_SOCKET=/run/supervisor/core.sockin the HA Core container environment. - HA Core creates the socket at
/run/supervisor/core.sock(bind-mounted from the devcontainer host at/run/supervisor/). - The supervisor connects to the socket via the host-side path
SOCKET_CORE = Path("/run/os/core.sock").
In the devcontainer, /run/os/ does not exist and the supervisor container does not have /run/supervisor in its bind-mount list (only /run/docker.sock, /run/dbus, /run/udev). The socket exists on the devcontainer host but is invisible to the supervisor container. The proxy's pre-flight check_api_state() call fails, and the proxy raises HTTP 502 for every add-on API request.
Two-part fix
Part A — supervisor_api_patch.py: Force TCP mode when SUPERVISOR_DEV=1 by short-circuiting use_unix_socket:
@property
def use_unix_socket(self) -> bool:
if self.sys_dev or not self.supports_unix_socket: # <-- sys_dev added
return False
...
This makes the supervisor use http://homeassistant:8123 (TCP on the hassio bridge network) instead of the Unix socket. HA Core's API is reachable via TCP from the supervisor container at 172.30.32.1:8123.
Part B — /run/os/core.sock symlink: Created on the devcontainer host as a belt-and-suspenders measure for any code path that bypasses the use_unix_socket guard:
Part B's symlink is host-side only; the hassio_supervisor container never had /run/os in its own mount list, so on its own it never actually reached the supervisor process. Part A (the API patch) is the one that matters.
4. supervisor_run silently exits before the supervisor ever starts (no TTY)¶
Symptom
Running supervisor_run non-interactively (e.g. from postStartCommand, which has no controlling terminal) prints:
...and then just stops. Docker is running fine, but hassio_supervisor never gets created — no error, no stack trace, nothing.
Root cause
start_docker() in /etc/supervisor_scripts/common ends with stty sane to reset the terminal after dockerd's output. The whole script (and the common file it sources) runs under set -e. When there is no TTY on stdin, stty sane exits non-zero, and set -e kills the script right there — before run_supervisor is ever called. This is invisible when running the task from an interactive VS Code terminal (which has a TTY), which is why it went unnoticed for a long time; it only bites in non-interactive invocations such as postStartCommand.
Fix
Make the call non-fatal: stty sane 2>/dev/null || true.
Applying the patches (current mechanism)¶
As of v1.5.6, the patches are applied statically, not at runtime:
devcontainer_bootstrap(runs once, viapostCreateCommand) patches three things in the container image, before the supervisor is ever started:/usr/bin/supervisor_rungetssupervisor_firewall_patch.pyandsupervisor_api_patch.pyadded as read-only bind mounts ontosupervisor/host/firewall.pyandsupervisor/homeassistant/api.pyinside thehassio_supervisorcontainer'sdocker runcommand. The patched code is active from the very first boot — nodocker cp+ restart dance needed./etc/supervisor_scripts/commongets thestty sanefix from section 4 above.- Both patches are idempotent (safe to re-run
bash devcontainer_bootstrapby hand). postStartCommand(runs on every container start/resume) creates/run/supervisorand/run/os(withsudo—/runis root-owned, a plainmkdirfails silently otherwise) and the/run/os/core.socksymlink, then startssupervisor_runin the background vianohupifhassio_supervisorisn't already running — so Home Assistant comes up automatically when the devcontainer starts, same as before v1.5.6.
apply_supervisor_patch.sh and the old docker cp-based flow described above are no longer wired into the startup path; they're kept as a manual fallback (e.g. to hot-patch a running supervisor without restarting the devcontainer).
Files changed¶
| File | Purpose |
|---|---|
.devcontainer.json |
postStartCommand: sudo mkdir for /run/supervisor + /run/os, core.sock symlink, auto-starts supervisor_run in the background if not already running |
devcontainer_bootstrap |
postCreateCommand: bind-mounts both patch files into supervisor_run's docker run, patches the stty sane bug in /etc/supervisor_scripts/common |
supervisor_firewall_patch.py |
Patched host/firewall.py — skips gateway check in dev mode |
supervisor_api_patch.py |
Patched homeassistant/api.py — forces TCP transport in dev mode |
apply_supervisor_patch.sh |
Manual fallback: hot-patches and restarts an already-running hassio_supervisor without recreating the devcontainer |
Upstream status¶
docker_gateway_unprotecteddevcontainer incompatibility is tracked in home-assistant/supervisor#6650 (comment by AlCalzone).- Unix socket fallback for devcontainers has no upstream issue filed as of 2026-04-11.
Once the upstream supervisor adds a proper devcontainer escape hatch, these patches can be removed.