部署配方 · 自动化
机架上的 Home Assistant:房子里不依赖云服务的那部分
有的家庭里,机架存在的理由就是 Home Assistant;在这个家里,它还是加装第二块固态盘的理由。它也是这里唯一需要 host 网络、特权容器和两个 USB 棒透传的服务——这让它成了真正和这台机器绑定的一个,而不是随便搬到哪儿都能跑的容器。
| 容器镜像 | ghcr.io/home-assistant/home-assistant:2026.9.1 |
|---|---|
| 宿主端口 | :8123/tcp, :5353/udp |
| 数据目录 | /srv/homelab/homeassistant/config |
| 内存预留 | 620 MB |
| CPU 上限 | 0.60 vCPU (cpus: "0.60") — recorder writes and automations are bursty |
| 更新节奏 | monthly. Home Assistant ships a major release every month with breaking changes listed in the release notes; read them, because integrations are renamed and YAML keys are retired with little ceremony |
| ARM 兼容 | arm64 native; the Z-Wave and Zigbee radios need a USB stick passed through, which is the one part of this stack that is genuinely host-dependent |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :8123 | 8123 | tcp | 仅局域网 | the web UI and the companion app's API |
| :5353 | 5353 | udp | 仅局域网 | mDNS discovery for Cast devices and ESPHome nodes |
部署步骤
建配置目录,让容器自己拥有它
homeassistant 镜像以 root 写入,配置目录不可写时它会拒绝运行。这是这里少数几个配置目录故意归 root 所有的服务之一。
run sudo mkdir -p /srv/homelab/homeassistant/config sudo chown -R root:root /srv/homelab/homeassistant在启动任何东西之前,先给无线电设备稳定名字
用 lsusb 找到棒的厂商和产品 id,写好 udev 规则,重载,确认符号链接存在。事后才做这一步意味着所有设备都要重新配对。
run lsusb | grep -i -E 'zigbee|conbee|sonoff|zwave' ls -l /dev/zigbee /dev/ttyUSB* sudo udevadm control --reload-rules && sudo udevadm trigger用 host 网络启动,并检查发现功能
桥接网络会破坏 Cast 设备和 ESPHome 节点的 mDNS 发现,表现出来像是设备离线。这里 host 网络不是可选项。
run cd /srv/homelab/homeassistant && docker compose up -d docker logs --tail 40 homeassistant # 打开 http://192.168.10.20:8123 创建所有者账号把密钥从 configuration.yaml 挪进 secrets.yaml
任何带令牌、密码或密钥的东西都放进 secrets.yaml,再用 !secret 引用。在创建这个文件的同一次提交里把它加进 .gitignore。
run cat > /srv/homelab/homeassistant/config/secrets.yaml <<'EOF' mqtt_password: CHANGE_ME zigbee_network_key: CHANGE_ME EOF chmod 600 /srv/homelab/homeassistant/config/secrets.yaml建立 git,并写一条能证明整条链路通的自动化
一个没有可用自动化的仓库,就是一个你不会维护的仓库。从「容器挂了」的通知开始,因为它同时也告诉你机架上其它服务什么时候出问题。
run cd /srv/homelab/homeassistant/config git init && printf 'secrets.yaml\n*.db\n*.db-*\n.homeassistant/\n' > .gitignore git add -A && git commit -m 'initial configuration'
难的是无线电,不是软件
Home Assistant 的其它部分都可以搬走,唯独无线电不行。Zigbee 和 Z-Wave 设备通过 USB 棒通信,棒对应一个设备路径,而一旦拔下来插到别的口上,这个路径就变了。解法是一条 udev 规则给棒一个稳定的名字,这样 compose 文件里永远写 /dev/zigbee,而不是今天 /dev/ttyACM0、明天 /dev/ttyUSB1。
Thread 和 Matter 设备走网络而不是走棒,这更整洁,但它们需要 IPv6,以及一个能跨容器边界工作的 mDNS 反射器——这也是它用 host 网络的又一个理由。
# /etc/udev/rules.d/99-zigbee.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="zigbee", GROUP="dialout", MODE="0660"
# 然后:sudo udevadm control --reload-rules && sudo udevadm trigger
# compose 文件里引用 /dev/zigbee放任不管,记录器会把固态盘吃掉
每一次实体状态变化就是一行记录。四十个传感器的房子每天产生几万行,默认配置保留十天,而且从不整理数据库文件,所以旧行删掉之后 SQLite 文件依然在变大。
两个设置能解决:十四天的清理加上自动整理,以及一个排除列表,排除那些频繁变化又说明不了任何问题的实体——笔记本充电器的功率监测、运行时长传感器、任何每秒都在更新的东西。
# configuration.yaml
recorder:
purge_keep_days: 14
auto_purge: true
auto_repack: true
exclude:
domains:
- updater
- automation
entity_globs:
- sensor.*_uptime
- sensor.*_linkquality
- sensor.*_rssi哪些自动化值得留着,哪些不值得
能活下来的自动化都是无聊的那种:灯光跟着太阳而不是钟表走、洗衣机停止耗电时发通知、冷柜温度无故上升时告警、每晚检查这台机架上其它容器是否还活着。
被删掉的是那些会让人意外的。任何仅凭一个传感器就锁门、关暖气或布防的操作,都应该要求第二个条件,并且能从实体开关上轻易覆盖。一个偶尔搞错家里有没有人的智能房子,比一个笨房子更糟。
docker exec homeassistant python -m homeassistant --script check_config -c /config
# 每次重启前跑一次;启动时的 YAML 错误会让界面停在最后一次成功的状态数据库、历史,以及没有它们会怎样
Home Assistant 的 SQLite 数据库保存状态历史和长期统计。丢了它是丢图表,不是丢配置——配置是 YAML 文件,实体从无线电回来。恢复之前值得知道这个区别:恢复配置目录就够让房子继续跑,历史是锦上添花。
反过来说,配置目录就是整个安装本身。它在 git 里,在每晚的归档里,也是房子着火、你只有三十秒时第一个该抓走的东西。
docker exec homeassistant sqlite3 /config/home-assistant_v2.db "VACUUM INTO '/config/backup.db'"
# 对一个正在被写入的数据库做一致性快照compose 文件
整份文件放在 /srv/homelab/home-assistant/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:2026.9.1
container_name: homeassistant
restart: unless-stopped
network_mode: host
privileged: true
environment:
TZ: Asia/Shanghai
volumes:
- /srv/homelab/homeassistant/config:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
devices:
- /dev/ttyACM0:/dev/ttyACM0
- /dev/ttyUSB0:/dev/ttyUSB0加固清单
- 实例只在局域网和 WireGuard 内可达,从不做端口转发——暴露的 Home Assistant 就是暴露的门锁
- 长期访问令牌按集成逐个签发,集成移除时一并吊销
- 密钥文件保存所有凭据,在 git 中按文件名排除,并配有 pre-commit 钩子,拒绝提交任何匹配密钥模式的内容
- 记录器只保留十四天,数据库不会无限膨胀——这既是可靠性措施,也是隐私措施
备份方案
配置目录是一个 git 仓库,密钥文件排除在外;内置备份则产出完整归档送到 NAS。两者都有用:git 历史告诉你改了什么,归档则能一步把实例恢复起来。
上线后验证
- 容器重启后 Zigbee 设备仍能上报状态,说明 udev 符号链接活了
- 开着排除列表跑一周后,记录器数据库小于 200MB
- check_config 通过,重启不会让界面停摆
- 故意停掉另一个服务时,「容器挂了」的自动化被触发,手机收到通知
踩过的坑
- USB 棒的 /dev 路径会在重启之间、以及换 USB 口之后改变。如果设备在重启后随机离线,原因就在这里——解法是 udev 规则,不是重启容器。
- privileged: true 加上设备透传,给了这个容器很大的能力。让它待在局域网里,令牌限定范围,也不要照着教程加上那条把反向代理暴露到公网的规则。
- 通过界面的文件编辑器引入的 YAML 错误,不会停掉正在运行的实例,而是会停掉下一次重启。重启前先跑 check_config,而不是等房子已经下线了才发现错误。
- 繁忙安装下默认记录器设置会让数据库无限增长。如果要在同一块固态盘上跑好几年,清理和整理设置不是可选项。
硬件与选型问答
- Home Assistant 该跑在容器里,还是该独占一台设备?
- 已经有现成机架、又能把 USB 棒透传过去,就跑容器。如果无线电必须放在房子里某个特定位置,就让它独占一台设备——把 Pi 4 放在网络柜里、Zigbee 棒接一根一米延长线,是很常见也很合理的安排;要让容器留在机架上、无线电在别处,就得用网络协调器。
- 不用云的话,它到底能控制多少东西?
- 所有讲 Zigbee、Z-Wave、Matter 或某种本地协议的都能控制。不行的只有那些只接受云 API 的家电——部分扫地机、部分洗衣机、大多数门铃——它们要么靠社区项目接进本地,要么就一直待在厂商的 App 里。
- 断网的时候会怎样?
- 本地控制照常:灯、传感器、自动化、局域网内的界面都在。停掉的是任何依赖云的部分,以及伴侣 App 的通知(如果它走厂商服务器)。在集成支持的情况下配好本地推送,并且在选择要自动化哪些设备时就把断网行为考虑进去。