# ESPHome Device Builder — IoT firmware dashboard for the ESH ESP32/ESP8266 fleet. # Host: esh-docker-vm (10.0.50.45). UI: http://10.0.50.45:6052 # # ⚠ privileged + network_mode: host are REQUIRED and must not be "hardened" away: # USB flashing needs the former, mDNS device discovery the latter. Removing # either breaks adoption and OTA. # # ⚠ The image tag is pinned deliberately. A bare `ghcr.io/esphome/esphome` # drifted this container a full year: docker pulled `latest` once when the # container was created (2026-04-20, from a layer already cached 2025-08-29) and # never re-pulled, leaving it on 2025.8.2 — twelve releases behind — which # silently failed config validation for every current Everything Presence # sensor. Bump this line on purpose; do not un-pin it. services: esphome: container_name: esphome image: ghcr.io/esphome/esphome:2026.8.2 volumes: # Was /path/to/esphome/config — the upstream template placeholder, taken # literally by docker. Nothing was broken (compose and the container # agreed) but /path/to is in none of restic's sources, so the configs and # secrets.yaml were absent from file-level backup. Moved 2026-09-14 to # match the sibling mosquitto pattern. - /opt/docker/conf/esphome:/config - /etc/localtime:/etc/localtime:ro restart: always privileged: true network_mode: host environment: # Values live in .env beside this file on the host (root:docker 0640) and # in Vaultwarden at esh-docker-vm/esphome-dashboard. Never inline them # here — this file is git-tracked. # # ⚠ Use the ESPHOME_-prefixed names, not bare USERNAME / PASSWORD. 2026.8.2 # still honours the bare names but logs a DEPRECATION on every start and # says they "will stop working in a future release" — i.e. a silent auth # loss on some later bump, on a privileged host-network container that can # flash firmware to anything on the LAN. Observed in the container log on # the 2025.8.2 -> 2026.8.2 upgrade, 2026-09-14. - ESPHOME_USERNAME=${ESPHOME_USERNAME} - ESPHOME_PASSWORD=${ESPHOME_PASSWORD} # # Device Builder 1.0.0 added a remote-build peer-link receiver that binds # 0.0.0.0:6055 and advertises itself over mDNS as _esphomebuilder._tcp. # It is ON BY DEFAULT — controllers/remote_build/_state.py declares # `remote_builds_enabled: bool = True` — so nobody enabled it; it arrived # with the rewrite. There is exactly one Device Builder here and builds # run locally in this container, so the feature has no role: it is # unrequested LAN reachability on a privileged host-network container # whose job is writing firmware to devices. # # RESOLVED 2026-09-14: remote-build is now genuinely OFF — # `remote_build/set_settings {enabled: false}` over the dashboard's /ws # API, persisted at `_remote_build.enabled` in /config/.device-builder.json. # Verified across a restart: 6055 absent from `ss`, and no peer-link # "site listening" line on the current boot. # # ⚠ The dashboard is STILL discoverable over mDNS and that is a separate, # milder thing. `helpers.dashboard_advertise` announces # `_esphomebuilder._tcp.local.` pointing at 6052 — the auth-gated # dashboard — independent of remote-build. It leaks existence, address, # dashboard id, Device Builder + ESPHome versions and hostname. Version # fingerprinting on the LAN, not reachability into the build subsystem. # An earlier revision of this comment claimed mDNS was gone; it was not, # and the claim came from a log grep rather than from the network. # # ⚠ Two switches, and only one of them closes the port. The obvious # candidate — `remote_build/set_offloader_settings {remote_builds_enabled}` # — is the OUTBOUND half (this dashboard sending builds to peers). Setting # it to false persists, reads back false, and leaves the receiver # listening. The port belongs to `ReceiverController.set_settings`, whose # own docstring calls it "the receiver-side `enabled` master switch". # Turning off the one named `remote_builds_enabled` looks like the fix and # is not. # # This line is now the BACKSTOP, kept deliberately. The off state lives in # a single JSON file whose in-code default is `enabled: True` # (controllers/remote_build/_state.py), and this module's stores # soft-recover to an empty model on a malformed blob rather than erroring # — so a lost or corrupt settings file silently re-enables remote-build. # With this set, that regression binds loopback instead of 0.0.0.0. - ESPHOME_REMOTE_BUILD_HOST=127.0.0.1 labels: - homepage.group=Apps - homepage.name=ESPHome - homepage.icon=si-esphome - homepage.description=IoT firmware dashboard (ESP32/ESP8266) - homepage.href=http://10.0.50.45:6052