Skip to content

Home Assistant Docker

ArtMG edited this page Oct 30, 2020 · 23 revisions

Home Assistant Docker

This article focusses on a single deployment method the home automation software called Home Assistant (HA or Hass).

See also:

Introduction

Docker three-tier containers

This procedure will get you working as quickly as possible with Home Assistant in Docker containers on Ubuntu Server. The deployment is for a vanilla install, but using three separate containers for the classic split of web front-end, application layer and database back-end. The intention is that the system configuration can be managed through a git repository, allowing you to come back and rebuild this deployment from scratch very quickly.

Initial install

This is using Ubuntu Server LTS as the OS under docker, for a combination of efficiency, flexibility and control. And because Ubuntu is a pleasantly familiar environment for me :)

  • Power on device
  • Press F11 (or DEL or other key) for boot options
  • Ubuntu Server 18.03.4 LTS live USB installer
  • user@hostname
  • password
  • install openssh
  • do NOT bother with docker snap (buggy config needs too much cleanup)

I was doing this on a NUC, but any kit that runs x86 will do

  • Check BIOS in dmesg to ensure your platform is current
  • Insert any communications hardware and check, e.g.
lsusb

SSH config and hardening

A sensible security recommendation is only to allow SSH logon using certificates. Generate yourself a certificate using ssh-keygen and upload the public key to the server.

ssh-copy-id -i ~/.ssh/mycertificate.pub user@hostname

  • log on to test that it works

ssh -i ~/.ssh/mycertificate.pub user@hostname

sudo sed -i "s/.*PasswordAuthentication.*/PasswordAuthentication no/g" /etc/ssh/sshd_config
sudo sed -i "s/.*X11Forwarding.*/X11Forwarding no/g" /etc/ssh/sshd_config
sudo tee -a /etc/ssh/sshd_config <<EOF!
AllowUsers=build
EOF!

sudo systemctl reload ssh.service
sshd -T

Container management

Install docker

For Ubuntu we install Docker CE (Community Edition)

Preferred way - docker repos

The recommended approach is to set up Docker’s own repositories and install from them. This is what most users do, for ease of installation and upgrade. See https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-docker-engine---community

# credit https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository
# combined with raspi flexibility https://withblue.ink/2019/07/13/yes-you-can-run-docker-on-raspbian.html

# prereqs
sudo apt update
sudo apt install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

# add signing key and repo for this system and release
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -
# add-apt-repository (package software-properties-common) not installed by default on Raspbian
echo "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
     $(lsb_release -cs) stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list

# now read including the ppa and get the packages
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io
sudo apt install -y docker-compose

sudo usermod -aG docker $USER

# test
sudo docker run hello-world
issues

If you get an error whilst installing docker-ce, check with journalctl -xe because it might be due to debian's switch from iptables to nftables https://wiki.debian.org/iptables

# switch back to the legacy system docker is expecting
update-alternatives --set iptables /usr/sbin/iptables-legacy

Alternative - get.docker.com

This 'convenience script' approach appears simpler to use but is not normally recommended outside dev and test environments.

curl https://get.docker.com | sh
sudo apt install -y docker-compose
sudo usermod -aG docker $USER
sudo docker run hello-world

DON'T use snap

Ubuntu installer offers you a tempting 'docker snap package' option. Most people used the 'don't bother with docker snap' approach. I managed to getdocker snap install to run containers using https://askubuntu.com/a/990058 but it still seemed like it might be buggy on permissions elsewhere. This was when I switched to the docker recommended approach above.

Install container mgmt gui

Using Portainer just to get a simple GUI view of docker. You could use Rancher as a front end to Kubernetes & suite, but that requires a DB and more resources so is probably over the top.

sudo docker volume create portainer_data
# run Portainer in detach mode
docker run -d -p 9000:9000 --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

Theoretically we could just roll this container in with the others below. But then we would loose the handy portainer diagnostic features whilst debugging our compose files.

So skip this bit for now.

tee example.portainer.docker-compose.yaml <<EOF!
version: '3'
services:
  portainer:
    container_name: portainer
    image: portainer/portainer
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    ports:
      - "9000:9000"
    restart: always
EOF!

The containers and configs

Environment variables

First get the information you need

id
lsusb

Now see what the current user and docker IDs are and put them into PUID and PGID below. Get the Vendor and Product IDs from lsusb

##### These are examples, tweak to your own circumstances

PUID=1000
PGID=133

VENDORID=10c4
PRODUCTID=8a2a

POSTGRES_DB=hass
POSTGRES_USER=homeassistant
POSTGRES_PASSWORD=my3pass7word
TZ=Europe/London

ROOTDIR=/srv/docker

##### optional items

DNSSRV=192.168.1.1
DNSDOM=my.home.net

User context issues

This configuration attempts to use least-privilege principles, executing as a regular user account instead of root where possible. The configuration and data folders are permissioned as the user, but it would appear that the Home Assistant Docker container currently disregards the PUID and PGID variables.

Of course the typical fallback is leave everything permissioned to run in the root context, but to try and avoid that we are using the technique developed in https://github.com/tribut/homeassistant-docker-venv

Also, if using any serial devices, such as Zigbee or ZHA adapters, we need to add udev rules to permit access. All of this is implemented in the user context temporary fix section in the config below. If you want to see the diagnostic steps taken to determine this fix see Issues and troubleshooting section at the end.

Clean previous attempts

If this is your first time, skip this section.

It's just here in case you are coming back up to here to retry after diagnostics or tweaks and you need to clean up any old setup beforehand...

#### Paste JUST THIS NEXT LINE to authenticate yourself first
sudo echo


cd $ROOTDIR/config/compose
docker-compose down
cd ~
sudo rm -rf $ROOTDIR
rm ~/cfg

Configuration

– now let's begin with the setup

#### Paste JUST THIS NEXT LINE to authenticate yourself first
sudo echo


#### Prepare area for config files
sudo mkdir -p $ROOTDIR/
# if issues consider 
# sudo chmod 777 -R $ROOTDIR/
sudo chown $USER:$USER -R $ROOTDIR/
mkdir -p $ROOTDIR/data/
mkdir -p $ROOTDIR/data/secrets
mkdir -p $ROOTDIR/config/
#sudo chown $USER:$USER -R $ROOTDIR/
# link back to user home for easy finding
ln -s $ROOTDIR/config ~/cfg



#### Home Assistant config files

mkdir -p $ROOTDIR/config/hass
cd $ROOTDIR/config/hass

tee secrets.yaml << EOF!
db_url: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postdb/${POSTGRES_DB}
EOF!

tee configuration.yaml <<EOF!
default_config:
recorder: 
  db_url: !secret db_url
#group: !include groups.yaml
#automation: !include automations.yaml
#script: !include scripts.yaml
EOF!

tee .gitignore <<EOF!
secrets.yaml
home-assistant_v2.db*
home-assistant.log
.uuid
.storage
.HA_VERSION
*.cer
*.key
EOF!


##### user context temporary fix

mkdir -p $ROOTDIR/config/hass/docker
cd $ROOTDIR/config/hass/docker

wget https://raw.githubusercontent.com/tribut/homeassistant-docker-venv/master/run
chmod 777 run

# get the Vendor and Product IDs from `lsusb`
sudo tee -a /etc/udev/rules.d/50-ttyusb.rules <<EOF!
KERNEL=="ttyUSB[0-9]*" \
, NAME="tts/USB%n" \
, SYMLINK+="%k" \
, ATTRS{idVendor}=="${VENDORID}" \
, ATTRS{idProduct}=="${PRODUCTID}" \
, MODE="0666"
EOF!



#### nginx config

mkdir -p $ROOTDIR/config/nginx
cd $ROOTDIR/config/nginx

tee nginx.conf <<EOF!
events { } 
http {
    server {
        listen 80;
        location / {
            proxy_pass http://homeassistant:8123/; 
            proxy_set_header Host \$host;
            proxy_http_version 1.1;
            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
            proxy_set_header Upgrade \$http_upgrade;
            proxy_set_header Connection “upgrade”;
        }
    }
}
EOF!
# NB: this has slashes before the dollars because it's being echo-ed
# you wont need these if you paste the config into an editor!


#### Docker compose files

mkdir -p $ROOTDIR/config/compose
cd $ROOTDIR/config/compose

tee .env <<EOF!
PUID=${PUID}
PGID=${PGID}
TZ=${TZ}
CONFDIR=$ROOTDIR/config
DATADIR=$ROOTDIR/data
POSTGRES_DB=${POSTGRES_DB}
POSTGRES_USER=${POSTGRES_USER}
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
EOF!

if [[ ${DNSSRV} ]] ; then
tee -a .env <<EOF!
DNSSRV=${DNSSRV}
EOF!
fi
if [[ ${DNSDOM} ]] ; then
tee -a .env <<EOF!
DNSDOM=${DNSDOM}
EOF!
fi

tee .gitignore <<EOF!
.env
EOF!




tee docker-compose.yaml <<EOF!
version: '3'
networks:
  webapp:
    driver: bridge
  appdb:
    driver: bridge
services:

  homeassistant:
    restart: unless-stopped
    image: homeassistant/home-assistant
    devices:
      - /dev/ttyUSB0:/dev/ttyUSB0
      - /dev/ttyUSB1:/dev/ttyUSB1
#      - /dev/ttyACM0:/dev/ttyACM0
    volumes:
      - \${CONFDIR}/hass:/config
      - /etc/localtime:/etc/localtime:ro
# leave this to avoid issues if USB device ids switch
      - /dev/serial/by-id/:/dev/serial/by-id/
    environment:
      - PUID=\${PUID}
      - PGID=\${PGID}
      - TZ=\${TZ}
# credit - https://community.home-assistant.io/t/97994/2
# omitting hardcoded image names as network auto creates aliases
# WARNING: reports of some USB devices failing with privileged: true
#    privileged: true
    networks:
      - webapp
      - appdb

##### temporary fix for user context in docker
    user: \${PUID}:\${PUID}
    command: /config/docker/run


  postdb:
    restart: unless-stopped
    image: postgres
    volumes:
      - \${DATADIR}/postgresql:/var/lib/postgresql/data
      - /etc/localtime:/etc/localtime:ro
    environment:
      - POSTGRES_DB=\${POSTGRES_DB}
      - POSTGRES_USER=\${POSTGRES_USER}
      - POSTGRES_PASSWORD=\${POSTGRES_PASSWORD}
      # troubleshooting - https://community.home-assistant.io/t/81388/15
      - TZ=\${TZ}
    networks:
      - appdb

  nginx:
    image: library/nginx:alpine
    volumes:
#      - \${CONFDIR}/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - \${CONFDIR}/nginx:/etc/nginx:ro
      - \${DATADIR}/secrets:/secrets:ro
    ports:
      - 80:80
      - 443:443
    networks:
      - webapp

EOF!


#### Now run the Compose 
# without the -d if you want to test the results interactively

docker-compose up -d

# docker-compose restart

alternative process

As mentioned before, the majority of the procedures above could be replaced by the repo https://github.com/artmg/home-assistant-docker

However there may be one or two useful notes above, and the majority of the rest of this article provides useful guidance on working with what that repo gets you started on.

Maintenance

Workflow

The above configration is the most basic. As you build on it, you will likely want to have a workflow using a git repository (which may be private)

To set this up, or to restore your workflow file from the repo see:

NB: This explicitly excludes your secrets.yaml file, which you want to keep safely - either maintain it manually or use a backup technique like the one below.

Local keys

If you followed the suggestions above to install SSH logon certificates, it becomes very straightforward to install other certificates you need.

# e.g. nginx ssl certificates
scp myWildcard.crt myServer:/srv/docker/data/secrets/wild.cer
scp myWildcard.key myServer:/srv/docker/data/secrets/wild.key

Diagnosing https/ssl issues

If you get issues with https connections after changing certificates or after altering nginx config to implement / reimplement ssl handshakes:

  • try clearing your browser data for the site
  • use the Browser developer's 'Inspect' Console to diagnose further

Backup

Because you have ssh set up with certificates, it make it very straightforward to pull configuration files to make a local backup. Note that you only need to back up the files excluded from your workflow, that can not be automatically regenerated.

# credit https://superuser.com/a/891486  https://stackoverflow.com/a/52705174
ssh login@host "tar -cf - -C /srv/docker/config/hass/ .storage/ secrets.yaml | gzip -9c | cat" > /file/on/local/machine/HA_BackUp_$(date +\%y\%m\%d-\%Hh\%Mm).tar.gz
  • Notes on config/hass subfolders
    • store items like HA certificates in a subfolder of .storage
      • this way the above code backs it up automatically
  • Backup status for data/* subfolders
    • portainer - no backup
      • simple procedure to recreate entering admin credentials
    • postgresql - no backup
      • transient data only, e.g. history of device states
    • nginx - no backup
      • unless you add certificates that are hard to re-install

Migration

As the code is all in containers, with the config and data separate, it makes the migration onto another platform reasonably straightforward. This is the case even if the underlying operating system and/or architecture changes (e.g. between 32-bit Raspbian on risc Arm to 64-bit Ubuntu on cisc AMD).

You can use a backup similar to the one above, however you might want to

  1. shut down the containers to queisce file access
  2. take a snapshot of the entire folder structure /srv/docker

You may choose to restore only parts of it, but you should have a full copy of the lot just in case.

Upgrade and downgrade

The process to upgrade and downgrade is the same, and is very straightforward:

cd $ROOTDIR/config/compose
docker-compose down
docker pull homeassistant/home-assistant
docker-compose up -d --build

If you want to upgrade all components in the stack (including nginx & postgre) then use docker-compose pull instead.

An image may be referenced in up to three separate places:

  • In the pull command,
  • in the container's image value in the compose file
  • and in the FROM command in the Dockerfile

where homeassistant/home-assistant implies the default suffix :latest so this will update to whatever is the latest version, first pulling the image and then rebuilding the container with it.

If you want to downgrade to a specific version, all you need do is edit the image (potentially in those three places) to add the version as a suffix, e.g.

FROM homeassistant/home-assistant:0.100.3

If you have not previously then pull this specific version. Finally use docker-compose as above to build using this image.

Postgres upgrade issues

Sometimes, with a major version upgrade to PostgreSQL, the server might not start up correctly, as it is expecting you to perform some manual upgrade process.

As an alternative, because Home Asssistant mainly uses SQL for transient data, you might consider renaming (for possible later deletion) the postgres data folder so it is recreated directly within the new version.

Upgrades behind firewalls

If the server where your images run is behind a firewall, you can

  • use a test instance that has internet access
    • for example a virtual machine using virtualbox
  • pull the images as above in the test instance
  • use portainer to view the images and export them
    • ensure you have enough disk space for the image export files
      • both on the server to create the tar and your own desktop to store them
  • on the live instance use portainer to upload the image tar files

Export built images

If your compose file has a build directive to add new features via a docker file, some of these might also require internet access to be installed. Therefore you should use compose's image directive to label up the built image, so you can easily identify the built image to export and then re-import inside the firewall.

Portainer update

Note that you can upgrade Portainer in the same way, using Pull Image on the latest, then Export/Import through the firewall. If you get any authentication issues when starting up the new version, then browse to http://docker-host:9000/#/init/admin to reset the Portainer environment.

Issues and troubleshooting

manual detach

Oops! I forgot to add the -d detach option to my docker-compose command - what now?

Permission denied on ZHA device

Symptoms

[homeassistant.config_entries] Error setting up entry /dev/ttyUSB1 for zha
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/serial/serialposix.py", line 244, in open
    self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
PermissionError: [Errno 13] Permission denied: '/dev/ttyUSB1'

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 190, in async_setup
    hass, self
  File "/usr/src/homeassistant/homeassistant/components/zha/__init__.py", line 107, in async_setup_entry
    await zha_gateway.async_initialize()
<snip>
  File "/usr/local/lib/python3.7/site-packages/serial/serialposix.py", line 247, in open
    raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
serial.serialutil.SerialException: [Errno 13] could not open port /dev/ttyUSB1: [Errno 13] Permission denied: '/dev/ttyUSB1'

This configuration attempts to use least-privilege principles, executing as a regular user account instead of root where possible.

Diagnostics

sudo apt-get purge modemmanager

Package 'modemmanager' is not installed, so not removed

ls -l /dev/serial/by-id

lrwxrwxrwx 1 root root 13 Sep 28 09:37 usb-Silicon_Labs_HubZ_Smart_Home_Controller_612016F1-if00-port0 -> ../../ttyUSB0
lrwxrwxrwx 1 root root 13 Sep 28 09:37 usb-Silicon_Labs_HubZ_Smart_Home_Controller_612016F1-if01-port0 -> ../../ttyUSB1

ls -la /dev/ttyU*

crw-rw---- 1 root dialout 188, 0 Sep 28 09:37 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 Sep 28 09:37 /dev/ttyUSB1

Tried these both in docker host prompt and with docker exec, with similar results.

udevadm info -a -n /dev/ttyUSB0 | grep '{interface}' | head -n1
udevadm info -a -n /dev/ttyUSB1 | grep '{interface}' | head -n1

    ATTRS{interface}=="HubZ Z-Wave Com Port"
    ATTRS{interface}=="HubZ ZigBee Com Port"

groups $USER
user : group adm cdrom sudo dip plugdev lxd docker
sudo usermod -a -G dialout $USER
groups $USER
user : group adm dialout cdrom sudo dip plugdev lxd docker


sudo lsof 2>/dev/null | grep ttyUSB

nothing is using the device

docker-compose.yaml
services:
  homeassistant:
    privileged: true
# help https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities

tried the privileged directive – no change in behaviour

sudo chmod 0666 /dev/ttyUSB1

Changing the permissions on the device works, but does not persist a reboot

Workaround

# get the Vendor and Product IDs from `lsusb`
sudo tee -a /etc/udev/rules.d/50-ttyusb.rules <<EOF!
KERNEL=="ttyUSB[0-9]*" \
, NAME="tts/USB%n" \
, SYMLINK+="%k" \
, ATTRS{idVendor}=="10c4" \
, ATTRS{idProduct}=="8a2a" \
, MODE="0666"
EOF!

Tried the above example of a workaround using udev rules and this works AND sticks. This is now implemented in the user context temporary fix section in the main Configuration section above.

Clone this wiki locally