Infrastructure

How I Actually Deploy Home Assistant Config Changes: Samba, Git, and a Loop I Trust

Independent technologist · 200+ HA devices · GriswoldLabs
9 min read

For my first year on Home Assistant, my deploy process was: open the file editor add-on, change the YAML, hit restart, and hope. Sometimes the hope was misplaced. The worst incident took the house down for an evening because I’d fat-fingered an indent in configuration.yaml, HA refused to boot, and I was fixing it over a phone screen while the family asked why the lights wouldn’t work.

The current setup has survived about 18 months without a repeat: the whole config lives in git, changes deploy over Samba from my desktop, nothing reloads until check_config passes, and I verify the write actually landed — because the one time I didn’t, Samba had silently stopped accepting my password and I “deployed” nothing at all, twice, while the deploy script cheerfully told me everything was valid.

This post is that workflow, including the boring parts that turn out to be the important parts.

The trap: editing the live config and praying

Every HA setup starts the same way — you edit the running system directly, through the UI editors or the file editor add-on. And to be fair, for a dozen automations it’s fine. The problems compound with scale:

  • There’s no undo. The UI has no history. When an automation that worked last month doesn’t now, you have nothing to diff against. Was it your edit? An HA upgrade? A renamed entity? You’re reconstructing the past from memory.
  • There’s no review step. The gap between “typed” and “live” is one click. Every typo ships instantly to the only production environment your family lives in.
  • You can’t answer “what changed?” — which is the first question in every debugging session, and the live-edit workflow throws the answer away.

The fix isn’t exotic. It’s the same thing that fixed this for software teams: version control and a deploy step. The trick is fitting it to HA’s quirks, because HA has opinions about how you’re allowed to touch it.

Layout: what’s in git, what’s ignored

My HA config is a git repo on my desktop — the copy on the HA box is just the deployed artifact. Structure:

ha-config/
├── configuration.yaml
├── automations/          # one file per automation area
│   ├── lighting.yaml
│   ├── security.yaml
│   └── lock_battery_alert.yaml
├── scripts/
├── packages/
├── dashboards/
├── secrets.yaml          # NOT tracked
└── .gitignore

The load-bearing line in configuration.yaml:

automation: !include_dir_merge_list automations/
script: !include_dir_merge_named scripts/

One file per topic instead of a 4,000-line automations.yaml. Diffs stay readable, and two changes in different areas never collide. The tradeoff to know about: once automations come from an !include_dir_merge_list, the UI automation editor can’t save them anymore — there’s no single editable file for it to write to. That’s a feature in this workflow (the repo is the only writer), but decide deliberately, because you’re choosing files-as-truth over the UI.

The .gitignore matters as much as the tracked files:

secrets.yaml
.storage/
*.db
*.db-wal
*.db-shm
*.log*
backups/
tts/
.cloud/

secrets.yaml stays out for the obvious reason — every credential in your smart home lives there, and config repos have a way of ending up on GitHub eventually. .storage/ stays out because it’s HA’s private state — entity registries, auth, UI-managed config — and it is not yours to edit; hand-modifying files in there is the fastest route to an unbootable instance. The databases and logs stay out because they’re gigabytes of churn with zero config value.

The test I apply: track what I wrote, ignore what Home Assistant writes.

Getting changes onto the box: Samba, SSH, or the VS Code add-on

You need a write path from where you edit to where HA runs. The realistic options:

The Samba share add-on exposes /config as a network share. This is my deploy path: the deploy step is smbclient (or a mounted share) copying changed files across. It’s protocol-boring, works from every OS, and needs no agent on the HA side beyond the add-on.

SSH (via the SSH & Web Terminal add-on) gets you rsync, which is strictly nicer for syncing a tree — but note that a stock HAOS box has no SSH open at all; ports 22/22222 answer nothing until you install and configure the add-on. If you’re comfortable there, rsync -av --exclude-from=.gitignore ./ ha:/config/ is a great deploy step.

The VS Code add-on is the middle path most people land on: a real editor, in the browser, editing live files, with HA-aware autocomplete. It’s genuinely good — but it’s an editor, not a workflow. It edits in place with no history, no review, no rollback. I know people who edit in the VS Code add-on and commit from an SSH session afterward, which works but inverts the safety: the change is live before it’s recorded.

And one path that doesn’t exist, despite being the first thing an API-minded person reaches for: the REST API is effectively read-only for configuration. It’s excellent for reads — states, templates, history — and it can trigger service calls, including the reload services I lean on below. But there is no supported endpoint that writes YAML config. Even the UI’s own automation editor endpoint stops working once your automations come from an include directory. I spent a real afternoon confirming this before accepting it: if you want to change config from outside, a file share or SSH is the way; the API is for reading state and pulling triggers.

The Samba failure that taught me to verify writes

Here’s the incident that shaped the paranoid part of the workflow.

My deploy script copied files over Samba, ran a config check, and printed “Config valid!” One week, two consecutive “deploys” did nothing. The automations I’d changed kept their old behavior. No error anywhere.

The cause: the Samba add-on’s stored password had gone stale after maintenance on the HA box — and Samba auth fails silently if your script doesn’t check for it. smbclient returned NT_STATUS_LOGON_FAILURE, my script didn’t inspect that particular exit path, no files were written… and then the config check ran against the local repo copy, which was of course valid. Every green light was telling the truth about the wrong thing. “Your YAML parses” and “your YAML is what the server is running” are different claims, and my script had quietly switched from the second to the first.

Three fixes, all of which I’d recommend even if you never hit this exact bug:

1. Probe auth before deploying. One cheap listing catches a dead credential before you trust it:

smbclient //192.168.1.50/config -U "homeassistant%$SMB_PASS" -c 'ls' > /dev/null \
  || { echo "SAMBA AUTH FAILED — aborting deploy"; exit 1; }

2. Verify the write landed. After copying, my script pulls one deployed file back and compares hashes with the local copy. Half a second, and it converts “the copy probably worked” into “the bytes on the server are my bytes.”

3. Validate on the box, not just locally. The config check has to run against /config on the HA machine (via the CLI: ha core check) — validating your local copy proves nothing about what’s deployed.

If a deploy ever “succeeds” but behavior doesn’t change, check the write path first, before you debug the YAML. The YAML you’re staring at may simply not be there.

The deploy loop

The full sequence, end to end — this is a small shell script, but the steps matter more than the tooling:

  1. Edit locally, commit. Every change is a commit with a message saying why. Future me debugging at midnight reads these like case notes.
  2. Snapshot before anything mutates. A quick backup via ha backups new --name "pre-deploy-$(date +%F)" (or the equivalent button). HA’s built-in backups are fine for this; the point is that “roll back” is always a real option, not a hope.
  3. Probe Samba auth (see above). Abort loudly on failure.
  4. Copy changed files to the share.
  5. Verify one round-tripped file hash.
  6. Run the config check on the box. ha core check — and actually gate on it. If it fails, the deploy stops here, and the running system keeps running the last good config. This single gate is what ended the fat-fingered-indent outages: broken YAML now fails at deploy time, not at 11 PM when HA restarts for an update.
  7. Reload the affected domain — don’t restart. Automations changed? POST /api/services/automation/reload (a long-lived token and one curl). Scripts, scenes, templates, groups all have equivalents. Reloads are ~instant and don’t drop the Zigbee network or blind every sensor the way a restart does. I restart only for the domains that require it: recorder, http, logger, new integrations.
  8. Verify behavior, not vibes. The script pulls the state of one touched automation via the REST API (this is where the read-only API shines) and confirms it exists and is on. For anything risky I then trigger it manually once.

Total wall time for a typical change: about 40 seconds, most of which is me writing a commit message. That’s the whole tax, and in exchange the config can never silently drift, never deploy broken, and never lose history.

What I’d change

Honest ledger, 18 months in:

I’d add the auth probe on day one. Everything else in the loop I built incrementally and that was fine — but the silent-write-failure class of bug is the one that costs you a weekend, because every indicator is green while nothing is true.

I’d split automations into per-area files earlier. I ran a single automations.yaml for months. Every diff was noise, and git blame was useless. The !include_dir_merge_list change took ten minutes and improved every subsequent day.

I’m still deciding whether the dashboards belong in the repo. I moved my Lovelace config to YAML mode for the same versioning benefits, and mostly like it — but it’s a bigger workflow change than it looks (you give up the UI dashboard editor entirely, and resource declarations move into YAML with it). That migration had enough of its own gotchas that it’s a separate post.

The philosophical shift is the real upgrade: the running Home Assistant box stopped being a pet I edit and became a target I deploy to. The repo is the house’s memory now. When something misbehaves, git log -p automations/lighting.yaml answers “what changed and when” in five seconds — and after enough midnight debugging sessions that started without that answer, I’m never giving it back.


Related guides: My Home Assistant backup strategy · Node-RED to native HA YAML · Storage-mode to YAML dashboards

Tags #home-assistant #configuration #git #workflow
Share X / Twitter Facebook
Keep reading