I run HomeAssistant Core on FreeBSD inside a jail managed with BastilleBSD — no Docker, no VM, just a lightweight VNET jail with its own IP on the LAN. This post covers the full setup: three jails, a bootstrap script, USB passthrough for Zigbee, and WiFi devices on the same subnet.
Architecture
FreeBSD 15.1-RELEASE Host
│
├── homeassistant (192.168.2.50) — HA Core :8123
├── mosquitto (192.168.2.52) — MQTT broker :1883
└── zigbee2mqtt (192.168.2.51) — Zigbee2MQTT, Sonoff USB passthrough
All three jails are VNET on the same /24 subnet. HA Core talks to Mosquitto over MQTT, Zigbee2MQTT publishes device states to Mosquitto, and HA subscribes. Shelly devices connect directly over WiFi and are auto-discovered by HA on the LAN.
Why jails?
- Native FreeBSD — no Docker overlay, no Linux emulation
- VNET gives each jail a real IP on the LAN — devices discover HA directly
- ZFS snapshots for quick rollbacks before upgrades
- Each service in its own jail — clean isolation, restart one without affecting others
Creating the jails
bastille create -V homeassistant 15.1-RELEASE 192.168.2.50/24 vtnet0
bastille create -V mosquitto 15.1-RELEASE 192.168.2.52/24 vtnet0
bastille create -V zigbee2mqtt 15.1-RELEASE 192.168.2.51/24 vtnet0
The -V flag gives each jail a VNET interface with its own IP and routing table — they live on the same broadcast domain as the host.
HomeAssistant jail
A bootstrap script handles the full setup:
- Repository: codeberg.org/dkade/BSD
- Method: Python 3.14 virtual environment under
/usr/local/etc/homeassistant/venv - User: dedicated
hasssystem user - Service: FreeBSD rc.d script using
daemon -u hass - Enable:
sysrc homeassistant_enable=YES
What the script does:
# Inside the jail as root
fetch https://codeberg.org/dkade/BSD/raw/branch/main/FreeBSD/install_ha_freebsd.sh
sh install_ha_freebsd.sh
It installs system packages (python314, gcc12, rust, dbus, libffi, openssl, etc.), creates a venv, and installs homeassistant along with numpy, zlib_ng, and isal for performance. The rc.d script starts HA as the hass user and listens on port 8123.
After first boot, complete the onboarding at http://192.168.2.50:8123.
Mosquitto jail
bastille console mosquitto
pkg install mosquitto
sysrc mosquitto_enable=YES
service mosquitto start
No authentication is needed on a local subnet — configure listener 1883 192.168.2.52 in /usr/local/etc/mosquitto/mosquitto.conf to bind to the jail IP.
Zigbee2MQTT jail
This jail needs access to the Sonoff Zigbee coordinator plugged into the host.
USB passthrough with devfs
On the host (/etc/devfs.rules):
[bastille_usb=101]
add path 'usbctl' mode 0660
add path 'usb/*' mode 0660
add path 'ugen*' mode 0660
add path 'cuaU*' mode 0660
add path 'ttyU*' mode 0660
Set the rule in /usr/local/bastille/jails/zigbee2mqtt/jail.conf:
devfs_ruleset = "bastille_usb";
Then find the Sonoff device:
# On the host
ls /dev/cuaU*
# /dev/cuaU0 <- this is the Sonoff
Restart the jail and you should see the device inside:
bastille restart zigbee2mqtt
bastille console zigbee2mqtt
ls /dev/cuaU*
Installing and configuring
bastille console zigbee2mqtt
pkg install node npm mosquitto-libs
npm install -g zigbee2mqtt
Create /usr/local/etc/zigbee2mqtt/configuration.yaml:
mqtt:
server: mqtt://192.168.2.52:1883
serial:
port: /dev/cuaU0
frontend:
port: 8080
rc.d script at /usr/local/etc/rc.d/zigbee2mqtt to start via daemon, then sysrc zigbee2mqtt_enable=YES && service zigbee2mqtt start.
Device integration
- Shelly (WiFi): connect them to your WiFi. HA auto-discovers them via the LAN once the Shelly integration is added in the UI.
- Zigbee (Sonoff sensors, bulbs, etc.): pair through the Zigbee2MQTT frontend (
http://192.168.2.51:8080). HA receives device states via MQTT auto-discovery.
Upgrading
# HA Core
bastille console homeassistant
service homeassistant stop
su -l hass -c \
'HOME=/usr/local/etc/homeassistant PIP_CACHE_DIR=/tmp/pip-cache \
/usr/local/etc/homeassistant/venv/bin/pip install --upgrade homeassistant'
service homeassistant start
# zigbee2mqtt
bastille console zigbee2mqtt
service zigbee2mqtt stop
npm update -g zigbee2mqtt
service zigbee2mqtt start
# mosquitto
bastille console mosquitto
pkg upgrade mosquitto
service mosquitto restart
Gotchas
- Python compilation: HA Core compiles C and Rust extensions during
pip install. Make suregcc12,rust,libffi, andopensslare installed in the jail before installing HA. - dbus: HA Core expects D-Bus to be running (
service dbus start). The rc.d script hasREQUIRE: dbus, so dbus starts first. - USB devfs: The devfs ruleset must be applied before the jail starts. Double-check the ruleset number matches in both
/etc/devfs.rulesand the jail config. - VNET routing: If jails can’t reach the internet for
pkg install, check thatgateway_enable="YES"is set on the host and NAT/pf is configured for the VNET subnet. - Persistent pip cache: The script sets
PIP_CACHE_DIR=/tmp/pip-cacheso you don’t re-download wheels on every upgrade.
Summary
Three FreeBSD jails managed with BastilleBSD running HA Core, Mosquitto, and Zigbee2MQTT — each with its own IP, rc.d service, and clean isolation. The bootstrap script at codeberg.org/dkade/BSD automates the HA jail from scratch, and the rest is just standard FreeBSD package management.
Disclaimer: I use AI as a productivity tool. For a senior engineer, AI is incredibly powerful as one can focus on the solution design and conceptualization and leave the boring part that is implementation to the AI.
