Loading...
Author Cloudapp
E.G.

Updating Home Assistant in Docker Safely: My Workflow With Backup, Test and Rollback

July 21, 2026
Table of Contents

My Home Assistant has run for years as a bare Docker container — no Supervisor, no HAOS, just the official image and a mount onto my config directory. It's lean and controllable, but it comes at a price: there is no "update" button in the UI and no automatic snapshot. A major update can break my KNX heating, the PV sensors and half a dozen HACS integrations all at once, and if I'm unlucky I only find out when the bathroom stays cold that evening.

After a few painful updates I settled on a fixed procedure that I run through stubbornly every single time. The core of it: order is everything, the container gets recreated instead of overwritten in place, and I note down the exact way back before I touch anything. Here is the complete flow.

Why the order trips almost everyone up

The most common mistake is to pull the Home Assistant core first and then wonder why HACS integrations are broken. It is actually safer the other way around: update the custom components and HACS first and test them against the still-running OLD core version, then raise the core. That cleanly separates the two sources of failure. If something breaks after the HACS update, you know it's the integration — not a core break you rolled in at the same time.

Concretely: I open HACS, update the pending custom components one by one (for me usually things like the Shelly or Huawei Solar integration), restart Home Assistant once — still on the old core — and check that everything works. Only when that intermediate state is clean do I go for the image.

Step 1 — full backup before touching anything

Before every update I back up the entire config directory. On a Docker install that's simply the folder you mount into the container — it holds configuration.yaml, the database, the .storage registries and all your YAML packages. If that folder is backed up, your entire state is backed up. Put the copy outside the container mount (a different directory or another host), not next to it.

Important: stop the container before the backup, or at least accept the live SQLite database. For a major update with a database migration, a clean snapshot of the old state is the only thing the migration won't carry along anyway.

Step 2 — note the old image ID (your real rollback)

This is the step almost every guide leaves out, and it's the most important one. "Rollback" in Docker does not mean "pull stable again" — that just fetches the new version. A real way back only works via the exact image ID your container ran on before the update. You record it before pulling anything:

sudo docker inspect homeassistant --format '{{.Image}}'
# Rollback: docker run ... <OLD_IMAGE_ID>

Save that ID somewhere (text file, note, whatever). As long as the old image hasn't been pruned locally, you can start a new container with <OLD_IMAGE_ID> instead of the stable tag at any time and be back on the old state within seconds — together with your (backed-up) old config folder.

Step 3 — recreate the container instead of in-place

Now the actual core update. I never update in place; I stop the container, pull the new image and recreate the container from scratch — with exactly the same mount and the same flags as before. That keeps the container state predictable and stops any old runtime configuration from dragging along:

sudo docker stop homeassistant
sudo docker pull ghcr.io/home-assistant/home-assistant:stable
sudo docker rm homeassistant && sudo docker run -d \
  --name homeassistant --restart unless-stopped --network host \
  -v /home/USER/homeassistant:/config \
  ghcr.io/home-assistant/home-assistant:stable

Replace /home/USER/homeassistant with your actual config path. The --network host mode is practically mandatory for many local integrations (discovery, Modbus, KNX); keep exactly the flags your old container had — a forgotten flag is its own class of "X stopped working after the update" bugs.

Step 4 — wait for the database migration

On a major update, Home Assistant migrates its database schema on first start. Depending on database size that can take several minutes, and during that time the API won't answer yet. Don't get impatient and restart the container — that aborts the migration mid-flight. Instead I watch for when the API comes back:

watch -n 5 'curl -s -m 3 http://HA_HOST:8123/api/ 2>/dev/null'

Replace HA_HOST with your instance's address. As soon as the command returns a JSON response instead of an empty line, the core is up and the migration is done. In parallel it's worth tailing the container log (docker logs -f homeassistant), which logs the migration as it runs.

Step 5 — the post-update checklist

A green API endpoint doesn't yet mean the smart home works. I keep a short checklist of the things that, in my experience, act up first after updates — especially anything that goes over local bus systems. I actively walk through it after every update:

- [ ] KNX thermostats respond (switch preset mode)
- [ ] Window sensors correct (all off after startup)
- [ ] Solar/PV data coming in
- [ ] HACS works
- [ ] Automations active (blind schedule, heating day/night)

I check the PV and Modbus sensors especially closely, because they're prone to being mapped differently after a core jump — how I wire them up in the first place is in the Modbus basics post. If something's off, I fall back to the rollback from step 2 instead of poking around in a live system.

When the update goes wrong: the rollback in practice

The way back is exactly the reverse of step 3, just with the old image ID. Stop and remove the container, restore the old config folder from the backup (important if the migration already touched the schema — a migrated database won't run reliably on the old core), and start a new container with <OLD_IMAGE_ID>. That's precisely why the backup and the noted image ID aren't an optional extra but the two things that separate a bad update evening from a completely wrecked weekend.

My self-sufficient energy setup, for example, hangs off a chain of Modbus sensors and template calculations — if the self-consumption ratio suddenly reads unavailable after the update, I know a data source has dropped out, and I decide from the checklist whether to fix forward or roll back. How those derived metrics are built is described in the autarky sensors post.

Frequently asked questions

Why not just use Watchtower for automatic updates?

Watchtower silently pulls the newest image and recreates the container — which is exactly the scenario I want to avoid. A major update with a database migration and potentially breaking HACS integrations is something I want to run in a controlled way, with a backup and a noted rollback ID, not automatically at three in the morning. For pure minor/patch releases Watchtower is tempting, but Home Assistant's stable tag doesn't distinguish patch from major — so I deliberately do it by hand.

Isn't the Home Assistant backup enough instead of a folder backup?

The built-in backup doesn't exist as a button in the pure Docker variant (without Supervisor) — it's a Supervisor feature. In a bare container, your config mount is the backup. That's exactly what makes it so simple: copying one directory secures your complete state including the database and .storage registries.

How long do I keep the old image?

Until the new update has proven itself over several days of normal operation. Only then do I clean up with docker image prune. Before that, the old image is your only seconds-long rollback — a few hundred megabytes of disk are well worth it. I note the image ID and date so I know which image belongs to which working config.

What if the API doesn't come up at all after the update?

Then look at the container log first (docker logs homeassistant). The most common causes after a major jump are a removed/renamed YAML option or a HACS integration that's incompatible with the new core. If the log shows a concrete error you can fix it surgically; if it's opaque or the downtime drags on, the clean move is the rollback from step 2 rather than minutes of guessing on a live system.

Related articles