Skip to content

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
Both are equally good, if you separate the containers make sure they share a network group, or you will need to define each container by 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 .