HomeAssistant Assist
Since I host my HomeAssistant instance with Docker I miss out on the supervisor features and one click apps. Here is how to setup HomeAssistant, Whisper voice to text, openWakeWord, Piper text to speech, and ESPHome.
On the official HA documentation they recommends using two options for the container that I personally don't use:
privileged: true
ports: host
privileged: true
- Grants a Docker container root capabilities to all devices on the host system. This is handy for just plug and play of USB devices but since I only pass USB devices with proper permissions to my container, this is not needed.
ports: host
- Host network mode for a container, that container's network stack isn't isolated from the Docker host (the container shares the host's networking namespace), and the container doesn't get its own IP-address allocated.
The Stack
You can define each container in their own folder and docker compose file or you can make one file and run it as a stack.
docker/
└── homeassistant
└── docker-compose.yaml
homeassistant/
├── esphome
│  └── docker-compose.yaml
├── ha
│  └── docker-compose.yaml
├── openwakeword
│  └── docker-compose.yaml
├── piper
│  └── docker-compose.yaml
└── whisper
└── docker-compose.yaml
IP:port
rather than their DNS names whisper, piper,...
totally up to you.
Note: I build some of the containers locally to include the package 'netcat' to perform healthchecks on the container. This guide is written to include the upstream images so the healthcheck on Whisper, Piper, and openWakeWord WILL FAIL unless you build them locally, and update the image reference in docker-compose file.
tldr: Just give me what I want!
Complete stack docker-compose.yaml
version: "3"
services:
###
# HomeAssistant
###
homeassistant:
image: "ghcr.io/home-assistant/home-assistant:stable"
container_name: homeassistant
restart: unless-stopped
healthcheck:
test: curl --fail http://localhost:8123 || exit 1
retries: 3
interval: 10s
timeout: 5s
environment:
- TZ=America/Edmonton
- PUID=1000
- GUID=1000
- UMASK=007
- PACKAGES=iputils
volumes:
- ./homeassistant-run:/etc/services.d/home-assistant/run
- /etc/localtime:/etc/localtime:ro
- /mnt/homeassistant:/config
- /mnt/homeassistant/media:/media
ports:
- 8123:8123
###
# ESPHome
###
esphome:
image: "esphome/esphome:latest"
container_name: esphome
restart: unless-stopped
healthcheck:
test: curl --fail http://localhost:6052 || exit 1
retries: 3
interval: 10s
timeout: 5s
environment:
depends_on:
homeassistant:
condition: service_healthy
environment:
- USERNAME=admin
- PASSWORD=changeme
volumes:
- /mnt/homeassistant/esphome:/config
- /etc/localtime:/etc/localtime:ro
ports:
- 6052:6052
###
# Whisper
###
whisper:
image: "rhasspy/wyoming-whisper"
container_name: whisper
restart: unless-stopped
healthcheck:
test: echo '{ "type"':' "describe" }' | nc -w 1 localhost 10300 | grep "faster-whisper" > /dev/null || exit 1
retries: 3
interval: 10s
timeout: 5s
depends_on:
homeassistant:
condition: service_healthy
command: --model tiny-int8 --language en
volume:
- /mnt/homeassistant/whipser:/data
ports:
- 10300:10300
###
# openWakeWord
###
openwakeword:
image: "rhasspy/wyoming-openwakeword"
container_name: openwakeword
restart: unless-stopped
healthcheck:
test: echo '{ "type"':' "describe" }' | nc -w 1 localhost 10400 | grep "openwakeword" > /dev/null || exit 1
retries: 3
interval: 10s
timeout: 5s
depends_on:
homeassistant:
condition: service_healthy
whisper:
condition: service_healthy
command: --preload-model 'ok_nabu'
volumes:
- /mnt/homeassistant/openwakeword:/data
ports:
- 10400:10400
###
# Piper
###
piper:
image: "rhasspy/wyoming-piper"
container_name: piper
restart: unless-stopped
healthcheck:
test: echo '{ "type"':' "describe" }' | nc -w 1 localhost 10200 | grep "piper" > /dev/null || exit 1
retries: 3
interval: 10s
timeout: 5s
depends_on:
homeassistant:
condition: service_healthy
commnad: --voice en_US-lessac-medium
volumes:
- /mnt/homeassistant/piper:/data
ports:
- 10200:10200
HA
My compose file changes a bit from the original. I define my port for the container, I run a healthcheck
before raising the whole stack and make each container depend on the healthy status.
HomeAssistant docker-compose.yaml
version: "3"
services:
homeassistant:
image: "ghcr.io/home-assistant/home-assistant:stable"
container_name: homeassistant
restart: unless-stopped
healthcheck:
test: curl --fail http://localhost:8123 || exit 1
retries: 3
interval: 10s
timeout: 5s
environment:
- TZ=America/Edmonton
- PUID=1000
- GUID=1000
- UMASK=007
- PACKAGES=iputils
volumes:
- ./homeassistant-run:/etc/services.d/home-assistant/run
- /etc/localtime:/etc/localtime:ro
- /mnt/homeassistant:/config
- /mnt/homeassistant/media:/media
ports:
- 8123:8123
I've been experimenting with rootless Docker and rootless Podman. You will see under the volumes
I pass a script to the container and place it in /etc/services.d/home-assistant
. The script can be found here on GitHub and it allows us to run the container as non-root. You do not need to include this script in your container, you can still drop the priviledged: true
without this script.
ESPHome
ESPHome is incredible, we use it to flash the M5 ATOM Echo voice assistant, temp & humidity sensors, and so much more.
ESPHome docker-compose.yaml
version: "3"
services:
esphome:
image: "esphome/esphome:latest"
container_name: esphome
restart: unless-stopped
healthcheck:
test: curl --fail http://localhost:6052 || exit 1
retries: 3
interval: 10s
timeout: 5s
environment:
depends_on:
homeassistant:
condition: service_healthy
environment:
- USERNAME=admin
- PASSWORD=changeme
volumes:
- /mnt/homeassistant/esphome:/config
- /etc/localtime:/etc/localtime:ro
ports:
- 6052:6052
Whisper - Speech to Text
Wyoming protocol server for faster whisper speech to text system.
I use the official docker image but modify the Dockerfile so I can slip netcat
into the container. This allows me to run my healthcheck and find if the container is properly up. This healthcheck can be performed on Piper
, openWakeWord
, and Whisper
, you just need to build each container with netcat
, and adjust the grep
command to look for each service.
Whisper docker-compose.yaml
version: "3"
services:
whisper:
image: "rhasspy/wyoming-whisper"
container_name: whisper
restart: unless-stopped
healthcheck:
test: echo '{ "type"':' "describe" }' | nc -w 1 localhost 10300 | grep "faster-whisper" > /dev/null || exit 1
retries: 3
interval: 10s
timeout: 5s
depends_on:
homeassistant:
condition: service_healthy
command: --model tiny-int8 --language en
volume:
- /mnt/homeassistant/whipser:/data
ports:
- 10300:10300
You can check out available models for Whisper here, choose the model best for your hardware and update the command
line for the container to reflect those changes.
Whisper Dockerfile
FROM debian:bullseye-slim
# Install Whisper
WORKDIR /usr/src
ARG WHISPER_VERSION='1.0.1'
RUN \
apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
netcat \
python3 \
python3-dev \
python3-pip \
\
&& pip3 install --no-cache-dir -U \
setuptools \
wheel \
&& pip3 install --no-cache-dir \
--extra-index-url https://www.piwheels.org/simple \
"wyoming-faster-whisper==${WHISPER_VERSION}" \
\
&& apt-get purge -y --auto-remove \
build-essential \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /
COPY run.sh ./
EXPOSE 10300
ENTRYPOINT ["bash", "/run.sh"]
sudo docker build -t local-whisper .
openWakeWord
Wyoming protocol server for the openWakeWord wake word detection system.
openWakeWord is an open-source wakeword library that can be used to create voice-enabled applications and interfaces. It includes pre-trained models for common words & phrases that work well in real-world environments.
openWakeWord docker-compose.yaml
version: "3"
services:
openwakeword:
image: "rhasspy/wyoming-openwakeword"
container_name: openwakeword
restart: unless-stopped
healthcheck:
test: echo '{ "type"':' "describe" }' | nc -w 1 localhost 10400 | grep "openwakeword" > /dev/null || exit 1
retries: 3
interval: 10s
timeout: 5s
depends_on:
homeassistant:
condition: service_healthy
whisper:
condition: service_healthy
command: --preload-model 'ok_nabu'
volumes:
- /mnt/homeassistant/openwakeword:/data
ports:
- 10400:10400
Once again we will modify the Dockerfile to include netcat for our healthcheck.
openWakeWord Dockerfile
FROM debian:bullseye-slim
ARG TARGETARCH
ARG TARGETVARIANT
# Install openWakeWord
WORKDIR /usr/src
ARG OPENWAKEWORD_LIB_VERSION='1.8.1'
RUN \
apt-get update \
&& apt-get install -y --no-install-recommends \
netcat \
python3 \
python3-pip \
libopenblas0 \
\
&& pip3 install --no-cache-dir -U \
setuptools \
wheel \
&& pip3 install --no-cache-dir \
--extra-index-url https://www.piwheels.org/simple \
"wyoming-openwakeword==${OPENWAKEWORD_LIB_VERSION}" \
\
&& rm -rf /var/lib/apt/lists/*
WORKDIR /
COPY run.sh ./
EXPOSE 10400
ENTRYPOINT ["bash", "/run.sh"]
sudo docker build -t local-openwakeword .
Piper - Text to Speach
A fast, local neural text to speech system that sounds great and is optimized for the Raspberry Pi 4.
Piper docker-compose.yaml
version: "3"
services:
piper:
image: "rhasspy/wyoming-piper"
container_name: piper
restart: unless-stopped
healthcheck:
test: echo '{ "type"':' "describe" }' | nc -w 1 localhost 10200 | grep "piper" > /dev/null || exit 1
retries: 3
interval: 10s
timeout: 5s
depends_on:
homeassistant:
condition: service_healthy
commnad: --voice en_US-lessac-medium
volumes:
- /mnt/homeassistant/piper:/data
ports:
- 10200:10200
You can check out different voice selections here, and change the command to reflect the voice you want. Like the other containers I build it locally so I can add netcat into the container for my healthcheck.
Piper Dockerfile
FROM debian:bullseye as build
ARG TARGETARCH
ARG TARGETVARIANT
ENV LANG C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install --yes --no-install-recommends \
build-essential cmake ca-certificates curl pkg-config git && \
apt-get install netcat
WORKDIR /build
COPY ./ ./
RUN cmake -Bbuild -DCMAKE_INSTALL_PREFIX=install
RUN cmake --build build --config Release
RUN cmake --install build
# Do a test run
RUN ./build/piper --help
# Build .tar.gz to keep symlinks
WORKDIR /dist
RUN mkdir -p piper && \
cp -dR /build/install/* ./piper/ && \
tar -czf "piper_${TARGETARCH}${TARGETVARIANT}.tar.gz" piper/
# -----------------------------------------------------------------------------
# FROM debian:bullseye as test
# ARG TARGETARCH
# ARG TARGETVARIANT
# WORKDIR /test
# COPY local/en-us/lessac/low/en-us-lessac-low.onnx \
# local/en-us/lessac/low/en-us-lessac-low.onnx.json ./
# # Run Piper on a test sentence and verify that the WAV file isn't empty
# COPY --from=build /dist/piper_*.tar.gz ./
# RUN tar -xzf piper*.tar.gz
# RUN echo 'This is a test.' | ./piper/piper -m en-us-lessac-low.onnx -f test.wav
# RUN if [ ! -f test.wav ]; then exit 1; fi
# RUN size="$(wc -c < test.wav)"; \
# if [ "${size}" -lt "1000" ]; then echo "File size is ${size} bytes"; exit 1; fi
# -----------------------------------------------------------------------------
FROM scratch
# COPY --from=test /test/piper_*.tar.gz /test/test.wav ./
COPY --from=build /dist/piper_*.tar.gz ./
sudo docker build -t local-piper .