Skip to content
twinkling.top服务Home AssistantEN
板型Pi 4B 4GB and up; below that the recorder's database writes cause visible UI pauses
内核6.6.51+rpt-rpi-v8
空闲温度48 °C
内核架构arm64
参考机:Pi 4B 8GB / Bookworm 64-bit

部署配方 · 自动化

机架上的 Home Assistant:房子里不依赖云服务的那部分

有的家庭里,机架存在的理由就是 Home Assistant;在这个家里,它还是加装第二块固态盘的理由。它也是这里唯一需要 host 网络、特权容器和两个 USB 棒透传的服务——这让它成了真正和这台机器绑定的一个,而不是随便搬到哪儿都能跑的容器。

ghcr.io/home-assistant/home-assistant:2026.9.1 · 2026-09-23 ·

Home Assistant rack plate: board stack and host ports
服务参数
容器镜像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暴露用途
:81238123tcp仅局域网the web UI and the companion app's API
:53535353udp仅局域网mDNS discovery for Cast devices and ESPHome nodes

部署步骤

  1. 建配置目录,让容器自己拥有它

    homeassistant 镜像以 root 写入,配置目录不可写时它会拒绝运行。这是这里少数几个配置目录故意归 root 所有的服务之一。

    run
    sudo mkdir -p /srv/homelab/homeassistant/config
    sudo chown -R root:root /srv/homelab/homeassistant
  2. 在启动任何东西之前,先给无线电设备稳定名字

    用 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
  3. 用 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 创建所有者账号
  4. 把密钥从 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
  5. 建立 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 网络的又一个理由。

shell
# /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 文件依然在变大。

两个设置能解决:十四天的清理加上自动整理,以及一个排除列表,排除那些频繁变化又说明不了任何问题的实体——笔记本充电器的功率监测、运行时长传感器、任何每秒都在更新的东西。

shell
# 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

哪些自动化值得留着,哪些不值得

能活下来的自动化都是无聊的那种:灯光跟着太阳而不是钟表走、洗衣机停止耗电时发通知、冷柜温度无故上升时告警、每晚检查这台机架上其它容器是否还活着。

被删掉的是那些会让人意外的。任何仅凭一个传感器就锁门、关暖气或布防的操作,都应该要求第二个条件,并且能从实体开关上轻易覆盖。一个偶尔搞错家里有没有人的智能房子,比一个笨房子更糟。

shell
docker exec homeassistant python -m homeassistant --script check_config -c /config
# 每次重启前跑一次;启动时的 YAML 错误会让界面停在最后一次成功的状态

数据库、历史,以及没有它们会怎样

Home Assistant 的 SQLite 数据库保存状态历史和长期统计。丢了它是丢图表,不是丢配置——配置是 YAML 文件,实体从无线电回来。恢复之前值得知道这个区别:恢复配置目录就够让房子继续跑,历史是锦上添花。

反过来说,配置目录就是整个安装本身。它在 git 里,在每晚的归档里,也是房子着火、你只有三十秒时第一个该抓走的东西。

shell
docker exec homeassistant sqlite3 /config/home-assistant_v2.db "VACUUM INTO '/config/backup.db'"
# 对一个正在被写入的数据库做一致性快照

compose 文件

整份文件放在 /srv/homelab/home-assistant/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。

compose.yaml
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 的通知(如果它走厂商服务器)。在集成支持的情况下配好本地推送,并且在选择要自动化哪些设备时就把断网行为考虑进去。