Why Self-Host Home Automation with Home Assistant on Docker

Home Assistant is the leading open-source home automation platform, integrating over 2,000 devices and services into a single privacy-focused dashboard. Running it locally means your lights, sensors, and automations keep working even when your internet goes down — no cloud dependency, no subscription fees, no data leaving your network.

Home Assistant Installation Methods Compared

Home Assistant offers several installation paths:

Method Pros Cons Best For
Home Assistant OS Full supervisor, add-ons, OTA updates Heavy VM, less flexible Plug-and-play users
Home Assistant Supervised Add-ons on existing OS Complex setup, fragile Advanced OS users
Home Assistant Core (Docker) Lightweight, full control, easy backups Manual add-on management Homelab operators
Home Assistant Container Pre-packaged Docker image Same as Core Docker-first setups

For a Proxmox homelab where you already run Docker, Home Assistant Core in Docker is the sweet spot. You get:

  • Minimal resource footprint (~500 MB RAM at idle)
  • Complete control over the stack (MQTT, Zigbee2MQTT, backups)
  • Easy migration and version pinning via Docker tags
  • Integration with your existing Traefik, monitoring, and backup infrastructure

Architecture Overview

The stack runs in a single Docker Compose project on a Debian 12 VM or LXC container:

Internet → Traefik (SSL) → Home Assistant (port 8123)
                              ├── Mosquitto (MQTT broker, port 1883)
                              ├── Zigbee2MQTT (serial via USB)
                              └── Code Server (optional — config editor)

Hardware Requirements

  • VM/LXC: 2 vCPUs, 2 GB RAM, 20 GB disk (Debian 12)
  • Zigbee Coordinator: Sonoff Zigbee 3.0 USB dongle (CC2652P) or Conbee II
  • Network: Static IP on your management VLAN

Proxmox VM Prerequisites for Home Assistant Docker

Create the Debian 12 VM

On your Proxmox host, create a lightweight VM:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Download Debian 12 cloud image
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2

# Create VM with 2 vCPUs, 2 GB RAM, 20 GB disk
qm create 200 --name hass --cores 2 --memory 2048 --net0 virtio,bridge=vmbr0
qm importdisk 200 debian-12-generic-amd64.qcow2 local-lvm
qm set 200 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-200-disk-0
qm set 200 --ide2 local-lvm:cloudinit
qm set 200 --boot order=scsi0
qm set 200 --agent enabled=1
qm resize 200 scsi0 20G
qm start 200

Alternatively, use an LXC container for lighter overhead (no separate kernel):

1
2
3
4
pveam download local debian-12-standard_12.7-1_amd64.tar.zst
pct create 200 /var/lib/vz/template/cache/debian-12-standard_12.7-1_amd64.tar.zst \
  --storage local-lvm --rootfs 20 --memory 2048 --cores 2 --net0 name=eth0,bridge=vmbr0,ip=dhcp
pct start 200

Enable USB Passthrough for Zigbee Coordinator

If using a VM, attach the USB Zigbee dongle:

1
2
3
4
5
6
# On Proxmox host, find the USB device
lsusb
# Example output: Bus 001 Device 005: ID 1a86:55d4 QinHeng Electronics SONOFF Zigbee 3.0 USB dongle

# Attach to the VM
qm set 200 --usb0 host=1a86:55d4

For an LXC container, bind-mount the USB device:

1
2
3
4
5
6
7
# On Proxmox host, find the USB tty device
ls -l /dev/serial/by-id/
# Example: usb-ITEAD_SONOFF_Zigbee_3.0_USB_Dongle_V2_...

# Edit LXC config
echo 'lxc.cgroup2.devices.allow: c 188:* rwm' >> /etc/pve/lxc/200.conf
echo 'lxc.mount.entry: /dev/serial/by-id/usb-ITEAD_SONOFF_Zigbee_3.0_USB_Dongle_V2_... dev/ttyZigbee none bind,optional,create=file' >> /etc/pve/lxc/200.conf

Install Docker on the VM/LXC

SSH into the new VM/LXC and install Docker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apt update && apt upgrade -y
apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" \
  > /etc/apt/sources.list.d/docker.list
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker

Docker Compose Configuration for Home Assistant Stack

Create the project directory and files:

1
2
mkdir -p /opt/homeassistant/{config,mqtt,zigbee2mqtt}
cd /opt/homeassistant

Create docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
services:
  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:stable
    container_name: homeassistant
    restart: unless-stopped
    volumes:
      - ./config:/config
      - /etc/localtime:/etc/localtime:ro
      - /run/dbus:/run/dbus:ro
    ports:
      - "127.0.0.1:8123:8123"
    environment:
      - TZ=America/Santo_Domingo
    depends_on:
      mosquitto:
        condition: service_healthy
    networks:
      - hass-net
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "1.0"

  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mosquitto
    restart: unless-stopped
    volumes:
      - ./mqtt/config:/mosquitto/config
      - ./mqtt/data:/mosquitto/data
      - ./mqtt/log:/mosquitto/log
    ports:
      - "127.0.0.1:1883:1883"
    healthcheck:
      test: ["CMD", "mosquitto_sub", "-t", "$$SYS/broker/version", "--retained-only", "-C", "1", "-W", "5"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - hass-net
    deploy:
      resources:
        limits:
          memory: 128M
          cpus: "0.25"

  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:latest
    container_name: zigbee2mqtt
    restart: unless-stopped
    volumes:
      - ./zigbee2mqtt/data:/app/data
      - /run/udev:/run/udev:ro
    devices:
      - /dev/ttyZigbee:/dev/ttyZigbee
    environment:
      - TZ=America/Santo_Domingo
    depends_on:
      mosquitto:
        condition: service_healthy
    networks:
      - hass-net
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: "0.5"

networks:
  hass-net:
    driver: bridge

Expose Home Assistant and Mosquitto only on localhost — Traefik handles external access. Zigbee2MQTT does not need port exposure.

Mosquitto MQTT Broker Setup for Home Automation

Create the Mosquitto configuration:

1
mkdir -p /opt/homeassistant/mqtt/{config,data,log}

Create /opt/homeassistant/mqtt/config/mosquitto.conf:

1
2
3
4
5
6
7
8
9
listener 1883 127.0.0.1
allow_anonymous false
password_file /mosquitto/config/passwd
persistence true
persistence_location /mosquitto/data
log_dest file /mosquitto/log/mosquitto.log
log_type all
connection_messages true
log_timestamp true

Create MQTT users:

1
2
3
4
docker compose run --rm mosquitto mosquitto_passwd -c /mosquitto/config/passwd hass
# Enter password when prompted

docker compose run --rm mosquitto mosquitto_passwd -b /mosquitto/config/passwd zigbee2mqtt z2m_secret_pass

Restart Mosquitto to load the config:

1
2
docker compose up -d mosquitto
docker compose logs mosquitto

Zigbee2MQTT Docker Integration with USB Passthrough

Zigbee2MQTT bridges your Zigbee coordinator to MQTT, allowing Home Assistant to control all Zigbee devices through a single integration.

Create /opt/homeassistant/zigbee2mqtt/data/configuration.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
homeassistant: true
permit_join: false
mqtt:
  base_topic: zigbee2mqtt
  server: mqtt://mosquitto:1883
  user: zigbee2mqtt
  password: z2m_secret_pass
serial:
  port: /dev/ttyZigbee
  adapter: zstack
frontend:
  port: 8080
experimental:
  new_api: true
advanced:
  log_level: info
  network_key: GENERATE_ME
  pan_id: GENERATE_ME
  channel: 15
  transmit_power: 20
  report: true
device_options:
  legacy: false

Generate the network key and PAN ID:

1
2
3
4
5
6
# Generate 16-byte hex key for Zigbee network
openssl rand -hex 16
# Example output: 1a2b3c4d5e6f708192a3b4c5d6e7f809

# Generate PAN ID (2 bytes, between 0x0001 and 0xFFFE)
echo "0x$(openssl rand -hex 2)"

Replace GENERATE_ME in the configuration with your generated values. The channel setting should match the least congested 2.4 GHz channel in your environment. Channels 15, 20, and 25 are preferred because they avoid common Wi-Fi channels (1, 6, 11).

Pairing Zigbee Devices

To pair a new device, temporarily enable joining:

1
2
3
# Via MQTT
mosquitto_pub -h 127.0.0.1 -u hass -p YOUR_PASSWORD \
  -t zigbee2mqtt/bridge/request/permit_join -m '{"value": true, "time": 120}'

Or set permit_join: true in the configuration, pair your devices, then set it back to false. Never leave permit_join enabled permanently — it is a security risk.

Verify devices appear via MQTT:

1
docker compose exec mosquitto mosquitto_sub -t zigbee2mqtt/# -v

Home Assistant Core Configuration and Integrations

Home Assistant auto-generates a basic configuration.yaml on first launch. Start the stack to initialize it:

1
2
docker compose up -d
docker compose logs -f homeassistant

Wait for the log to show:

Home Assistant Core is running! Listening on http://0.0.0.0:8123

Access the VM IP on port 8123 (or via Traefik once configured). Complete the initial onboarding — create your user account, set your location, and name your home.

Adding Integrations

From the Home Assistant web UI, navigate to Settings → Devices & Services → Add Integration:

  1. MQTT — Click configure, enter mosquitto as broker, port 1883, username hass, and the MQTT password. No SSL needed since Mosquitto binds to localhost.
  2. Zigbee2MQTT — Search and add. It auto-discovers via MQTT topics. All paired Zigbee devices appear automatically.

Essential configuration.yaml Additions

Over time, add these to /opt/homeassistant/config/configuration.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Home Assistant configuration.yaml — add to existing file

# Set a more specific time zone
homeassistant:
  latitude: !secret latitude
  longitude: !secret longitude
  unit_system: metric
  time_zone: America/Santo_Domingo
  name: GnTech Home

# Enable energy dashboard
energy:

# HTTP configuration (for Traefik proxying)
http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16

# History and recorder
recorder:
  db_url: !secret recorder_db_url
  purge_keep_days: 30

# Logbook
logbook:

# System health
system_health:

# Update notifications
updater:

# Sun tracking for automations
sun:

Using Secrets

Create /opt/homeassistant/config/secrets.yaml to keep sensitive values out of configuration.yaml:

1
2
3
latitude: 18.4861
longitude: -69.9312
recorder_db_url: !env_var RECORDER_DB_URL

Home Assistant’s !env_var directive reads environment variables from the Docker container. Set them under environment: in the compose file.

Traefik Reverse Proxy Configuration for Home Assistant

Add Traefik labels to the homeassistant service in docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
services:
  homeassistant:
    # ... existing config ...
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.hass.rule=Host(`hass.yourlab.com`)"
      - "traefik.http.routers.hass.entrypoints=websecure"
      - "traefik.http.routers.hass.tls=true"
      - "traefik.http.routers.hass.tls.certresolver=letsencrypt"
      - "traefik.http.services.hass.loadbalancer.server.port=8123"
      - "traefik.http.middlewares.hass-headers.headers.customrequestheaders.X-Forwarded-For={{clientIp}}"
      - "traefik.http.routers.hass.middlewares=hass-headers@docker"

If using a Traefik dynamic config file instead:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# /opt/traefik/config/homeassistant.yml
http:
  routers:
    hass:
      rule: "Host(`hass.yourlab.com`)"
      entryPoints: ["websecure"]
      tls:
        certResolver: letsencrypt
      service: hass
      middlewares:
        - hass-headers

  services:
    hass:
      loadBalancer:
        servers:
          - url: "http://homeassistant:8123"

  middlewares:
    hass-headers:
      headers:
        customRequestHeaders:
          X-Forwarded-For: "{clientIp}"

Add an external network to the compose file so Traefik and Home Assistant can communicate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
networks:
  hass-net:
    driver: bridge
  traefik-net:
    external: true
    name: traefik-net

services:
  homeassistant:
    networks:
      - hass-net
      - traefik-net

The http.use_x_forwarded_for and trusted_proxies settings in configuration.yaml ensure Home Assistant sees the real client IP behind Traefik.

Home Assistant Backup Automation and Recovery

Volume Backup Script

Create /opt/homeassistant/backup.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
# Home Assistant Docker backup script
BACKUP_DIR=/opt/homeassistant/backups
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"

cd /opt/homeassistant

# Stop the stack for consistent snapshots
docker compose pause

# Archive config directories
tar czf "$BACKUP_DIR/hass-config-$TIMESTAMP.tar.gz" \
  --exclude="config/.storage/auth*" \
  config/ \
  zigbee2mqtt/data/ \
  mqtt/config/

# Backup Home Assistant database separately (can be large)
docker compose exec -T homeassistant tar czf - /config/home-assistant_v2.db \
  > "$BACKUP_DIR/hass-db-$TIMESTAMP.tar.gz"

# Resume the stack
docker compose unpause

# Keep only last 14 days of backups
find "$BACKUP_DIR" -name "hass-*.tar.gz" -mtime +14 -delete

echo "Backup completed: $TIMESTAMP"

Set execute permission and test:

1
2
chmod +x /opt/homeassistant/backup.sh
/opt/homeassistant/backup.sh

Automated Daily Backup with Systemd Timer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cat > /etc/systemd/system/hass-backup.service << 'EOF'
[Unit]
Description=Home Assistant Docker backup
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/opt/homeassistant/backup.sh
WorkingDirectory=/opt/homeassistant
User=root
EOF

cat > /etc/systemd/system/hass-backup.timer << 'EOF'
[Unit]
Description=Daily Home Assistant backup

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=30m

[Install]
WantedBy=timers.target
EOF

systemctl daemon-reload
systemctl enable --now hass-backup.timer

Restore Procedure

To restore from backup on a fresh installation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Stop the stack
docker compose down -v

# Restore config
tar xzf /path/to/backup/hass-config-20250609-070000.tar.gz -C /opt/homeassistant/

# Restore database
docker compose up -d homeassistant
docker compose exec -T homeassistant bash -c 'tar xzf - -C /' \
  < /path/to/backup/hass-db-20250609-070000.tar.gz

# Restart everything
docker compose up -d

Docker Image Updates with Watchtower

Add Watchtower to monitor the Home Assistant Docker images:

1
2
3
4
5
6
7
8
docker run -d \
  --name watchtower \
  --restart unless-stopped \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --interval 86400 \
  --cleanup \
  --scope hass-stack

Tag your compose services with com.centurylinklabs.watchtower.scope=hass-stack to scope updates:

1
2
3
4
5
6
7
services:
  homeassistant:
    labels:
      - "com.centurylinklabs.watchtower.scope=hass-stack"
  zigbee2mqtt:
    labels:
      - "com.centurylinklabs.watchtower.scope=hass-stack"

Conclusion

Running Home Assistant Core with Docker on Proxmox gives you a production-grade home automation stack that is fully local, fully under your control, and easy to maintain alongside your other homelab services.

The stack cost:

  • 2 vCPUs, ~1.5 GB RAM at idle (with Zigbee2MQTT and Mosquitto)
  • 4 Docker containers, all on a single bridge network
  • Zero cloud dependency for core functionality

From here, explore:

  • Energy dashboard — connect Shelly or Zigbee energy monitoring plugs
  • Local voice pipeline — run Whisper + Piper for fully offline voice control
  • ESPHome — flash ESP32/ESP8266 devices as custom sensors and relays
  • Node-RED — visual automation flows beyond Home Assistant’s built-in automations

For official documentation, visit home-assistant.io and the Zigbee2MQTT docs.