-
Notifications
You must be signed in to change notification settings - Fork 3
Home Assistant Docker
This article focusses on a single deployment method the home automation software called Home Assistant (HA or Hass).
See also:
-
https://github.com/artmg/home-assistant-docker
- as an alternative to these procedures, a repo you can clone or fork
- maybe the quickest way to Home Assistant working on Docker platform
-
https://github.com/artmg/MuGammaPi/wiki/home-assistant-venv
- deploying Home Assistant using Python Virtual Environment (venv)
-
https://github.com/artmg/MuGammaPi/wiki/home-assistant
- Comparison of various deployment methods
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.
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
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
For Ubuntu we install Docker CE (Community Edition)
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
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
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
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.
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
- browse to http://hostname:9000
- set up your admin password
- connect to local
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!
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
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.
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
– 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
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.
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.
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
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
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
- store items like HA certificates in a subfolder of .storage
- 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
- portainer - no backup
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
- shut down the containers to queisce file access
- 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.
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.
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.
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
- ensure you have enough disk space for the image export files
- on the live instance use portainer to upload the image tar files
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.
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.
Oops! I forgot to add the -d
detach option to my docker-compose
command - what now?
- Press
CTRL-Z
to send to background -
kill -9 %1
to send kill signal to the compose command - this leaves, however, the containers themselves running without interuption
- credit - https://github.com/docker/compose/issues/4560#issuecomment-504766005
[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.
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
# 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.