Skip to content

A Fully Local Home Assistant Voice Assistant in 20 Lines of Docker Compose

Whisper, Piper and Assist on plain CPU, no cloud and no GPU. The compose file, about 1.1 GB of RAM, a 0.6-second best case, and the two gotchas that have nothing to do with Home Assistant.

Stacked glowing shipping containers beside a small microphone puck send sound waves into a cozy room.

A Fully Local Home Assistant Voice Assistant in 20 Lines of Docker Compose

If you've been meaning to move your voice assistant off Alexa or Google and keep everything in the house, the best "start here" walkthrough I've read this month is Tom Cocking's post on ExitCode0. It's short, it has real numbers, and it's honest about the two things that broke.

Here's what stood out to me, and what I'd take from it.

The idea

A Home Assistant voice assistant is three pieces:

  1. Whisper turns your speech into text.
  2. Assist, Home Assistant's built-in conversation agent, figures out what you meant and does it.
  3. Piper turns the reply back into speech.

Whisper and Piper talk to Home Assistant over the Wyoming protocol. Both run fine on a CPU. No GPU, no cloud, no LLM.

Cocking built the whole stack on October 11 on Home Assistant 2026.10.1, on a shared CPU-only Docker host, and timed it.

The compose file

This is his file. It's Home Assistant Container plus two Wyoming containers:

services:
  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:stable
    ports:
      - "8123:8123"
    volumes:
      - ./ha-config:/config
    environment:
      - TZ=Europe/London
    restart: unless-stopped

  wyoming-whisper:
    image: rhasspy/wyoming-whisper
    volumes:
      - ./whisper-data:/data
    command: ["--model", "base-int8", "--language", "en"]
    restart: unless-stopped

  wyoming-piper:
    image: rhasspy/wyoming-piper
    volumes:
      - ./piper-data:/data
    command: ["--voice", "en_GB-alba-medium"]
    restart: unless-stopped

Change TZ to your own time zone. If you'd rather have an American voice, Piper has those too; swap the --voice value.

One detail I like: the Wyoming containers don't publish any ports. Home Assistant reaches them by service name on the compose network, so nothing else on your LAN can talk to them. That's a small, sensible bit of hygiene.

If you run Home Assistant OS instead, you don't need this file. Whisper and Piper are available as apps there. The compose route is for people on Container installs, which don't get apps.

Wiring it up

The rest happens in the Home Assistant UI:

  1. Add the Wyoming Protocol integration twice. Whisper at host wyoming-whisper, port 10300. Piper at host wyoming-piper, port 10200.
  2. Build the assistant under Settings > Voice assistants. Conversation agent: Home Assistant. Speech-to-text: faster-whisper. Text-to-speech: piper. Make it the preferred assistant.
  3. Expose what it can control under Settings > Voice assistants > Expose. Assist only touches entities you've exposed. Start with a few lights.

The numbers

These are Cocking's measurements, not mine:

  • RAM, idle after a command: Home Assistant 360 MB, Whisper (base-int8) 480 MB, Piper 280 MB. He says budget about 1.5 GB free.
  • Typed command to finished spoken reply: 1.4 seconds. Typing skips Whisper, so this measures Assist plus Piper.
  • Full spoken pipeline: 0.6 seconds from the first audio byte, on a 1.5-second recorded clip of synthesized speech.

He's upfront that the 0.6 seconds is "a clean best case." A real mic in a noisy kitchen will be slower and less accurate. Still, that's a useful baseline: a modest CPU box can do local voice for simple commands without anyone waiting around.

Gotcha 1: "lights" vs. "light"

His first spoken test was "turn on the kitchen light." Whisper base-int8 heard "turn on the kitchen lights." Assist then read "kitchen" as the Kitchen area, found nothing in that area it could switch, and failed.

Nothing was broken. Small Whisper models flip singular and plural on short phrases. His two fixes:

  1. Name entities so they can't collide with area names. He renamed "Kitchen light" to "Kitchen lamp," and it matched first time.
  2. Use a bigger Whisper model if accuracy matters more than speed.

I'd do the first one no matter what. Entity names that don't overlap with area names are just good Assist hygiene.

Gotcha 2: the mic button does nothing over plain HTTP

Click the microphone in the Assist dialog at http://192.168.x.x:8123 and nothing records. In his browser, the reply came back as [object Object].

That's the browser, not Home Assistant. Browsers only expose the microphone API on a "secure context," which means HTTPS or localhost. Typed commands still work over HTTP. Voice from the browser doesn't. He uses Tailscale to get HTTPS on his LAN, but any proper certificate setup does the job.

His line on this is the best advice in the post: "If you hit the mic problem first, you'll spend an hour blaming Wyoming. Don't."

What it can't do yet

There's no wake word in this setup. You press the mic button or a satellite's button. And the built-in agent only understands command-style phrases that match its templates. "Turn on the kitchen lamp" works. "Is it going to rain tomorrow, and should I close the windows?" doesn't.

That's where an LLM comes in. Cocking says an Ollama conversation agent is the next post in his series. Meanwhile, I wrote about another blogger's fully local LLM build on a 16 GB AMD card, which shows what that next step costs in hardware.

My take

This is the right order to do local voice. Get the dumb, fast, CPU-only pipeline working first. Fix your entity names. Get HTTPS. Then decide whether you actually need an LLM, or whether "turn on, turn off, what's the temperature" covers most of what your family says to a speaker anyway.

One more thing worth borrowing: the post ends with a ready-made prompt you can paste into a coding agent. It tells the agent to check whether Home Assistant is already running, which port 8123 is on, how much RAM is free, and whether you're on HTTPS, not to overwrite an existing compose file, and to "Show me the diff first." If you let an AI agent set up services on your home server, that last line is the habit to steal.

Permalink: https://homeautomationweekly.com/issues/25-home-assistant-local-voice-whisper-piper-docker