Skip to content

Home Assistant Docker Deploy — Full Guide

Prerequisites

  • A Linux box with 4 GB+ RAM and 20 GB+ free disk (RPi 4/5, J4125, or a mini PC)
  • Docker Engine 24+ and docker compose v2
  • A domain (optional but recommended for remote access)
  • ~2 hours of your time (30 min to deploy, 90 min to configure)

Step 1: Install Docker

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Verify
docker --version
docker compose version

# Optional: allow your user to run Docker without sudo
sudo usermod -aG docker $USER
# Log out and back in to apply

Option B: Yum on RHEL / Rocky Linux

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker

Option C: apt on Debian / Ubuntu

# Remove old versions
sudo apt remove -y docker docker.io containerd runc

sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker

Step 2: Configure Docker Daemon

Edit /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "dns": ["8.8.8.8", "1.1.1.1"]
}
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl status docker

Step 3: Install Nginx (Reverse Proxy)

sudo apt install -y nginx
sudo systemctl enable --now nginx

Nginx Server Block

Create /etc/nginx/sites-available/homeassistant:

upstream homeassistant {
    server 127.0.0.1:8123;
    keepalive 16;
}

server {
    listen 80;
    server_name homeassistant.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name homeassistant.example.com;

    # Certbot-managed certs
    ssl_certificate /etc/letsencrypt/live/homeassistant.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/homeassistant.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    access_log /var/log/nginx/ha.access.log;
    error_log  /var/log/nginx/ha.error.log warn;

    # Large uploads (add-on downloads, large camera streams)
    client_max_body_size 100m;

    location / {
        proxy_pass http://homeassistant;
        proxy_http_version 1.1;

        # Required HA headers — do not remove
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;

        # WebSockets (required for real-time updates)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;

        # Keepalive
        proxy_set_header Connection "";
    }
}
sudo ln -s /etc/nginx/sites-available/homeassistant /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 4: Configure SSL with Certbot

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d homeassistant.example.com

# Auto-renew
sudo systemctl enable --now certbot.timer
sudo certbot renew --dry-run

Step 5: Install Home Assistant

Create Data Directory

sudo useradd --system --uid 1000 homeassistant 2>/dev/null || true
sudo mkdir -p /opt/hass
sudo chown -R 1000:1000 /opt/hass

Run the Container

docker run -d \
  --name homeassistant \
  --restart=unless-stopped \
  --network=host \
  -e TZ=Asia/Shanghai \
  -e ALLOW_LIST=0.0.0.0/0 \
  -v /opt/hass:/config \
  homeassistant/amd64:latest

Verify

docker ps | grep homeassistant
docker logs -f homeassistant   # Watch startup logs

Once HA boots, open http://your-host-ip:8123 (or via your domain) and complete the onboarding wizard.

Step 6: Install Common Add-ons

Option A: Add-on Store in UI

  1. Settings → Add-ons & Integrations (if you installed with Supervisor)
  2. Settings → Add-ons → Add-on Store (Docker-only setup)

⚠️ Docker-only (HA Container) doesn't include the Supervisor. Add-ons like Mosquitto, Zigbee2MQTT, File Editor are not directly available. See manual add-on setup below.

Option B: Manual Setup via SSH

If you installed with Docker only (no Supervisor), install add-ons manually:

Mosquitto (MQTT broker):

mkdir -p /opt/addons/mosquitto
cd /opt/addons/mosquitto

wget https://github.com/MoEttaz/Mosquitto-Add-on-for-HA/releases/latest/download/docker-compose.yml
sed -i 's|./mosquitto/config:/mosquitto/config:/mosquitto/data|../../../addons/mosquitto/config:/mosquitto/config:ro ../../../addons/mosquitto/data:/mosquitto/data|g' docker-compose.yml

docker compose up -d
docker compose ps

Zigbee2MQTT:

mkdir -p /opt/addons/zigbee2mqtt
cd /opt/addons/zigbee2mqtt

wget https://raw.githubusercontent.com/koenkk/zigbee2mqtt-addon/master/docker-compose.yml
docker compose up -d

Home Assistant Backup (in case Supervisor is missing):

mkdir -p /opt/addons/backup
cd /opt/addons/backup

wget https://raw.githubusercontent.com/home-assistant/addons/master/backup/docker-compose.yml
docker compose up -d

Step 7: Configure Backup

backup.sh

Save as /usr/local/bin/backup-ha.sh:

#!/usr/bin/env bash
# Home Assistant automatic backup script
# Usage: backup-ha.sh [retention_days]

set -euo pipefail

RECORDS=200
BACKUP_DIR="/opt/hass/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
SNAPSHOT_FILE="${BACKUP_DIR}/ha_backup_${TIMESTAMP}.tar.gz"

# Ensure backup dir exists
mkdir -p "$BACKUP_DIR"

# 1. Create HA snapshot via API
echo "Creating HA snapshot..."
TOKEN="$(cat /opt/hass/.ha_token 2>/dev/null || true)"
curl -s -X POST \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name":"auto-backup-'$TIMESTAMP'"}' \
  http://127.0.0.1:8123/api/snapshot/create

# 2. Wait for snapshot to complete
sleep 10

# 3. Snapshot data is at /opt/hass/snapshot/
SNAPSHOT_ID=$(ls -t /opt/hass/snapshot/*.tar.gz 2>/dev/null | head -1 | xargs -I{} basename {} | xargs -I{} dirname {})

# 4. Copy latest snapshot to timestamped file
LATEST=$(ls -t /opt/hass/snapshot/*.tar.gz 2>/dev/null | head -1)
if [ -f "$LATEST" ]; then
  cp "$LATEST" "$SNAPSHOT_FILE"
  echo "Backup saved: $SNAPSHOT_FILE"
fi

# 5. Retention: keep last N snapshots
echo "Pruning old backups..."
find "$BACKUP_DIR" -name "ha_backup_*.tar.gz" -mtime +"${RECORDS}" -delete

# 6. Optional: sync to remote (uncomment to enable)
# rsync -avz "$SNAPSHOT_FILE" user@remote:/backups/ha/
chmod +x /usr/local/bin/backup-ha.sh

Cron Schedule

crontab -e
# Add daily at 03:00
0 3 * * * /usr/local/bin/backup-ha.sh 30

Step 8: Verify Everything Works

# HA is up
curl -I http://127.0.0.1:8123/

# Domain resolves
nslookup homeassistant.example.com

# HTTPS works
curl -I https://homeassistant.example.com/

# Cert is valid
openssl s_client -connect homeassistant.example.com:443 -servername homeassistant.example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates

# Webhook works (test with a simple HA webhook)
# POST to /api/webhook/YOUR_WEBHOOK_ID

# Docker container is healthy
docker ps
docker stats homeassistant

Docker vs Container vs Supervised Install — Comparison

Aspect HA OS HA Container HA Supervised
Base OS Raspberry Pi OS / J4125 image Any Linux Ubuntu / Debian
Add-on store ✅ Built-in ❌ Manual setup ✅ Built-in
OTA updates Automatic Manual docker pull Automatic
Resource usage Moderate Low High
Best for Home users Advanced users Advanced users who want add-ons
Backup options Snapshot + USB export DIY Snapshot + remote

5 Real Failure Modes

Failure 1: Port 8123 Already in Use

Symptom: Error: Bind for 0.0.0.0:8123 failed: port is already allocated.

Root cause: Another HA instance (HAOS leftover, snap, or previous Docker run) is still holding the port.

Fix:

sudo ss -lnt | grep 8123
# Kill the offending process
sudo lsof -i :8123
sudo systemctl stop homeassistant-os  # or snap stop homeassistant

Failure 2: Webhooks Not Working Behind Proxy

Symptom: webhook not found in HA logs, camera snapshots broken.

Root cause: Nginx isn't forwarding X-Forwarded-Proto / X-Forwarded-Host correctly.

Fix:

# Add these to your Nginx config
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Failure 3: WebSocket Keeps Dropping

Symptom: UI shows "Connection lost" every few minutes.

Root cause: Nginx's default proxy_read_timeout is 60s; WebSocket connections get killed.

Fix:

location / {
    proxy_pass http://homeassistant;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

Failure 4: Permissions Denied on Config Files

Symptom: Permission denied when HA writes to /opt/hass/.

Root cause: Container runs as 1000:1000 inside, but host directory owned by root or wrong UID.

Fix:

sudo chown -R 1000:1000 /opt/hass

Failure 5: SSL Certificate Expiry

Symptom: NET::ERR_CERT_DATE_INVALID on the HA site.

Root cause: Certbot auto-renewal is off or the renewal failed silently.

Fix:

sudo certbot renew
sudo systemctl status certbot.timer
# Test renewal
sudo certbot renew --dry-run