Home Assistant Container: Upgrading Thirteen Releases Across a Four-Hop Ladder
The thirteen-version maintenance debt
When operating Home Assistant as a dedicated IoT telemetry and environmental control service inside a standalone Docker container (homeassistant/home-assistant)—aggregating rack thermal sensors, UPS power metrics, and automated alerts—system updates do not come from a one-click dashboard button. Every update requires pulling a new container image, recreating the service, and running database migrations on startup.
Over thirteen months, a production installation had remained pinned to version 2025.9.4. Upstream releases had moved to 2026.10.4. Jumping straight across thirteen major versions is the single fastest way to brick an automation stack:
- Irreversible Database Migrations: The recorder component upgraded its SQLite schema from version 50 to 53. If an intermediate migration fails mid-stream, the SQLite file cannot be rolled back without cold backups.
- Python Runtime Shifts: The base container migrated from Python 3.13 to 3.14, introducing stricter asynchronous task constraints and deprecating legacy helper functions.
- The 2026.8 HTTP Gate Rewrite: In 2026.8, Home Assistant restructured its reverse-proxy security subsystem, migrating network parameters into internal storage files (
.storage/http). A failed migration turns external access through reverse proxies into immediateHTTP 400: Bad Requesterrors.
To eliminate risk, the upgrade was partitioned into a verified 4-hop ladder.
Isolating hazards across four hops
Official guidelines recommend stepping through no more than five to six minor releases per increment. The selected upgrade path isolated each architectural breaking change:
| Hop | Target Image | Primary Hazard Isolated | Verification Gate |
|---|---|---|---|
| 1 | 2025.12.5 | Recorder SQLite schema migration (v50 → v53) | Database schema verified, WAL clean |
| 2 | 2026.3.4 | Python 3.13 → 3.14 runtime & zstd container layers | Log inspection: 0 setup failures |
| 3 | 2026.8.3 | http: reverse-proxy trusted_proxies validation & .storage/http transition | Reverse proxy headers pass; HTTP 200 |
| 4 | 2026.10.4 | Current stable target & final auth cleanup | All 259 baseline entities verified |
Before pulling images, a cold snapshot of the /config volume was created with the container stopped, ensuring database consistency.
🔍 docker-compose.yml — Home Assistant container definition (14 lines)
services:
homeassistant:
container_name: home-assistant
image: homeassistant/home-assistant:2026.10.4
volumes:
- /volume1/docker/home-assistant/config:/config
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=Asia/Kuala_Lumpur
network_mode: host
restart: unless-stopped
stop_grace_period: 60s
Notice stop_grace_period: 60s: under large SQLite databases, Home Assistant requires adequate time to commit SQLite WAL pages and flush event buffers. The default 10-second Docker timeout frequently causes SIGKILL aborts (exit code 137).
The result
The upgrade proceeded through all four stages without rollback:
- Entity Preservation: The baseline entity count expanded from 259 to 265 (the six additional entries were new native power-distribution metrics), with zero lost automations or unavailable sensors.
- Recorder Health: The database transitioned across schema versions in less than one second, with total row counts growing cleanly from 249,875 to 249,897.
- Gateway Integrity: The reverse proxy gate continued to enforce mutual authentication, with
X-Forwarded-Forvalidation passing without configuration regression.
Dealing with flaky containers, cloud cost bloat, or unexplained outages?
Real production reliability comes from understanding the kernel and the protocol layers, not just restarting containers. I manage 162 containers across 3 hosts running 24/7 client workloads.