Speaches Whisper API with Docker: Your Own Speech-to-Text Server (Linux, Windows, Mac)
Before you start
- 13 min read
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
Speaches is an open-source server that gives you the same audio API as OpenAI, but runs Whisper models on your own computer, so your recordings never leave it.
Under the hood it uses faster-whisper for speech-to-text, and Kokoro and Piper for text-to-speech. Any tool that can talk to OpenAI's /v1/audio/transcriptions can talk to Speaches instead: you only change the base URL. Models load when the first request needs them and unload after a while without use, so a small box stays calm. People use it to transcribe meetings, add voice input to Open WebUI, and cut the per-minute bill of a cloud service.
| What | Value |
|---|---|
| Version used in this guide | v0.8.3 (last stable release, Sep 2025). The newest release on GitHub is v0.9.0-rc.3 (a release candidate, Dec 2025) |
| Image tags | ghcr.io/speaches-ai/speaches:0.8.3-cpu and ghcr.io/speaches-ai/speaches:0.8.3-cuda-12.6.3 (both checked in the registry on 2026-10-26) |
| Time needed | About 10 minutes, plus the model download |
| Ports | 8000 (API, docs at /docs, web UI) |
| Official docs | https://speaches.ai/ |
Why 0.8.3 and not the newest tag? The newest release is a release candidate with new diarization code, and one open issue (#679) reports unsafe model loading in it. The install problems people report (#671, #678) hit source installs, not the prebuilt image. We found no reports of data loss. So we pin the last stable release. If you need speaker diarization, try 0.9.0-rc.3-cpu and accept that it is a candidate.
One honest note: the repository looks quiet. The last commit we saw is from April 2026, and two issues (#659, #622) ask whether it is still maintained. It works today. If it stalls, plain faster-whisper is the fallback.
How it fits together#
Your app sends audio to the Speaches API on port 8000, Speaches loads a faster-whisper model from its cache, and the text comes back.
The model cache lives in a folder on your disk, so a restart does not download the models again.
Before you start#
You need Docker, about 4 GB of free disk and a few GB of RAM. A GPU is optional.
| System | Requirements | Test status |
|---|---|---|
| Linux | Docker Engine with the Compose plugin, bash, curl. For a GPU: NVIDIA driver plus NVIDIA Container Toolkit | Checked against the official docs |
| Windows | Docker Desktop with the WSL2 backend, PowerShell 7. For a GPU: NVIDIA driver with WSL2 GPU support (VERIFY on your machine) | Checked against the official docs |
| Mac | Docker Desktop, Terminal. No CUDA in containers, so use the CPU image | Checked against the official docs |
Check your tools:
docker --version docker compose version
Checklist:
- [ ] Docker is running
- [ ] Port 8000 is free on your machine
- [ ] You have a backup of any older
~/docker_volumes/speachesfolder
Tested on 2026-10-26: Linux = not run, Windows = not run, Mac = not run. The commands were checked against the official Speaches docs, the v0.8.3 source and the image registry (see Sources).
Step 1: Create the folder, the key and the Compose file#
Make a folder for the model cache, create a secret API key, and write a small compose.yaml that pins the image.
Models are big, so we keep the cache in ~/docker_volumes/speaches/hf-cache. The container runs as user ubuntu with uid 1000, so on Linux the folder must belong to uid 1000 or you get a permission error.
Linux#
mkdir -p ~/docker_volumes/speaches/hf-cache && cd ~/docker_volumes/speaches
sudo chown -R 1000:1000 hf-cache
echo "SPEACHES_API_KEY=$(openssl rand -hex 24)" > .env && chmod 600 .env
cat > compose.yaml <<'EOF'
services:
speaches:
image: ghcr.io/speaches-ai/speaches:0.8.3-cpu
container_name: speaches
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000"
environment:
- API_KEY=${SPEACHES_API_KEY}
- WHISPER__TTL=300
- LOG_LEVEL=info
volumes:
- ./hf-cache:/home/ubuntu/.cache/huggingface/hub
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
EOF
Have an NVIDIA GPU? Change the image to ghcr.io/speaches-ai/speaches:0.8.3-cuda-12.6.3 and add this block under the service:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
Windows (PowerShell)#
$d = "$HOME\docker_volumes\speaches"
New-Item -ItemType Directory -Force "$d\hf-cache" | Out-Null
Set-Location $d
$bytes = New-Object byte[] 24
[System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
"SPEACHES_API_KEY=" + (($bytes | ForEach-Object { $_.ToString('x2') }) -join '') | Out-File .env -Encoding ascii
@'
services:
speaches:
image: ghcr.io/speaches-ai/speaches:0.8.3-cpu
container_name: speaches
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000"
environment:
- API_KEY=${SPEACHES_API_KEY}
- WHISPER__TTL=300
- LOG_LEVEL=info
volumes:
- ./hf-cache:/home/ubuntu/.cache/huggingface/hub
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
'@ | Set-Content compose.yaml -Encoding utf8
For a GPU, use the 0.8.3-cuda-12.6.3 image and the deploy: block from the Linux step. Windows bind mounts on NTFS can be slow. For heavy use, create the folder inside WSL2 instead (VERIFY on your setup).
Mac (Terminal)#
Same commands as Linux, but skip the sudo chown line (Docker Desktop handles the permissions). Keep the CPU image.
mkdir -p ~/docker_volumes/speaches/hf-cache && cd ~/docker_volumes/speaches echo "SPEACHES_API_KEY=$(openssl rand -hex 24)" > .env && chmod 600 .env # then create compose.yaml exactly as in the Linux step (CPU image)
Check it worked: ls shows .env, compose.yaml and hf-cache. Never commit .env.
Step 2: Start Speaches#
Start the container and wait for the health check.
Linux#
docker compose up -d docker compose ps curl -s http://localhost:8000/health
Windows (PowerShell)#
docker compose up -d docker compose ps curl.exe -s http://localhost:8000/health
Mac (Terminal)#
Same commands as Linux:
docker compose up -d docker compose ps curl -s http://localhost:8000/health
Check it worked: docker compose ps shows speaches as running (and healthy after a short wait), and /health answers with an OK status. The first start pulls the image, so give it a minute. Interactive API docs are at http://localhost:8000/docs.
Step 3: Download a speech model#
Speaches downloads a model when you ask for it. Ask once, then it stays in your cache folder.
Systran/faster-whisper-small is a good first choice: small, multilingual and fast on a CPU. For better accuracy on a GPU, take Systran/faster-distil-whisper-large-v3 (the model the Open WebUI docs use). In version 0.8.3 there is no "download on startup" setting, so you download with one request.
Linux#
set -a; source .env; set +a curl -X POST -H "Authorization: Bearer $SPEACHES_API_KEY" \ http://localhost:8000/v1/models/Systran/faster-whisper-small curl -H "Authorization: Bearer $SPEACHES_API_KEY" http://localhost:8000/v1/models
Windows (PowerShell)#
$key = ((Get-Content .env) -replace '^SPEACHES_API_KEY=','').Trim()
$h = @{ Authorization = "Bearer $key" }
Invoke-RestMethod -Method Post -Headers $h -Uri http://localhost:8000/v1/models/Systran/faster-whisper-small
Invoke-RestMethod -Headers $h -Uri http://localhost:8000/v1/models | ConvertTo-Json -Depth 5
Mac (Terminal)#
Same commands as Linux:
set -a; source .env; set +a curl -X POST -H "Authorization: Bearer $SPEACHES_API_KEY" \ http://localhost:8000/v1/models/Systran/faster-whisper-small curl -H "Authorization: Bearer $SPEACHES_API_KEY" http://localhost:8000/v1/models
Check it worked: the model list now contains Systran/faster-whisper-small. To see everything you can download, ask /v1/registry?task=automatic-speech-recognition. Potential issue: a 422 error on this download call was reported (issue #655). If you hit it, use the speaches-cli tool from the docs: uvx speaches-cli model download Systran/faster-whisper-small.
Step 4: Transcribe your first file#
Send an audio file as a form upload, exactly like you would to OpenAI.
Use any short recording named audio.mp3 (wav, m4a and others also work).
Linux#
curl http://localhost:8000/v1/audio/transcriptions \ -H "Authorization: Bearer $SPEACHES_API_KEY" \ -F [email protected] \ -F model=Systran/faster-whisper-small \ -F response_format=json
Windows (PowerShell)#
PowerShell 7 can send the form itself:
Invoke-RestMethod -Method Post -Headers $h -Uri http://localhost:8000/v1/audio/transcriptions `
-Form @{ file = Get-Item .\audio.mp3; model = "Systran/faster-whisper-small" }
Windows PowerShell 5.1 has no -Form. Use curl.exe with the same -F options as the Linux command.
Mac (Terminal)#
Same commands as Linux.
Check it worked: you get a JSON answer with a text field (expected shape, from the OpenAI reference: {"text": "..."}).
Useful options, all from the OpenAI audio API:
language=ja(an ISO-639-1 code) is faster and more accurate than auto-detect. Leave it out to detect the language.response_format=verbose_jsonadds segments and timestamps.srt,vttandtextwork too.stream=truesends the text back piece by piece (server-sent events) while it is still being transcribed.- Translation to English uses
POST /v1/audio/translationswith the same form fields.
Step 5: Connect Open WebUI#
Point Open WebUI's speech-to-text engine at Speaches and talk to your chat.
The setup is the same on every system, because it is a Compose file.
Linux#
Both containers must be on the same Docker network so Open WebUI can reach http://speaches:8000. A simple way is to run them from one Compose file. Add this service next to speaches in compose.yaml (pin a release tag instead of main for a stable setup):
open-webui:
image: ghcr.io/open-webui/open-webui:main
restart: unless-stopped
ports:
- "127.0.0.1:3000:8080"
environment:
- AUDIO_STT_ENGINE=openai
- AUDIO_STT_OPENAI_API_BASE_URL=http://speaches:8000/v1
- AUDIO_STT_OPENAI_API_KEY=${SPEACHES_API_KEY}
- AUDIO_STT_MODEL=Systran/faster-whisper-small
volumes:
- ./open-webui:/app/backend/data
Windows (PowerShell)#
Same Compose service, then run docker compose up -d in $HOME\docker_volumes\speaches.
Mac (Terminal)#
Same commands as Linux: add the service and run docker compose up -d.
You can also set it in the UI: Admin Settings, Audio tab, engine "OpenAI", base URL http://speaches:8000/v1, your key, and the model name. The Speaches docs warn that the environment variables do not override settings you already saved in the UI. Download the model first (Step 3), or the first voice message fails.
Text-to-speech, realtime and languages#
The same server can also speak, and it has a realtime API.
- Text-to-speech. Download
speaches-ai/Kokoro-82M-v1.0-ONNX, thenPOST /v1/audio/speechwith a voice such asaf_heart. The docs say extra steps are needed for TTS (see the install page), andopusandaacoutput are not supported. Chinese TTS currently fails (issue #647). - Realtime API. Added in v0.8.0, including WebRTC. See the Speaches realtime docs.
- Languages. Whisper models are multilingual, including Japanese and Hindi. The
distilmodels are English-focused, so pickfaster-whisper-large-v3or a smaller multilingual model for other languages (VERIFY the model id in/v1/registry).
Which model size?#
These are rough ballparks from general knowledge, not from the Speaches docs (VERIFY with the benchmark table in the faster-whisper README):
| Model | Rough VRAM (fp16) | Good for |
|---|---|---|
| tiny, base | about 1 GB | Quick tests, weak accuracy |
| small | about 2 GB | Good CPU choice |
| medium | about 5 GB | Better accuracy |
| large-v3 | 5 to 10 GB | Best accuracy |
| distil-large-v3 | less than large-v3 | Faster, English-focused |
Put it behind HTTPS with Traefik#
On a server with a domain, let Traefik handle HTTPS and remove Speaches' published port.
This assumes Traefik v3 already runs with the Docker provider, shares a Docker network called proxy, has an entrypoint called https and a certificate for your hostname. Local-only users can skip this section. Replace the ports: and add labels:
services:
speaches:
image: ghcr.io/speaches-ai/speaches:0.8.3-cpu
restart: unless-stopped
env_file:
- .env
environment:
- API_KEY=${SPEACHES_API_KEY}
volumes:
- ./hf-cache:/home/ubuntu/.cache/huggingface/hub
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.speaches.rule=Host(`stt.<your-domain>`)"
- "traefik.http.routers.speaches.entrypoints=https"
- "traefik.http.routers.speaches.tls=true"
- "traefik.http.services.speaches.loadbalancer.server.port=8000"
networks:
proxy:
external: true
Create the network once if needed: docker network create proxy. If you use an ACME resolver, add traefik.http.routers.speaches.tls.certresolver=<your-resolver>.
Check it worked: curl -I https://stt.<your-domain>/health answers, and a transcription request without the key is refused.
Security and testing#
Speaches has no login by default, so the key and the network are your job.
| Control | How |
|---|---|
| API key | API_KEY in .env (chmod 600 .env). Clients send Authorization: Bearer <key> |
| Network | Port bound to 127.0.0.1, or no host port at all behind Traefik |
| Web UI | Switch it off with ENABLE_UI=false if you do not use it |
| Models | Download only models you trust, from the official registry |
| Secrets | Never in the Compose file or in Traefik labels |
Keep port 8000 off the internet. Do not forward it on your router. If you must reach it from outside, use Traefik with HTTPS and an IP allowlist, or a VPN.
Test it: docker compose ps shows a healthy container, docker logs speaches --tail 50 shows no errors, a request with a wrong key is rejected, and a request with the right key returns text.
Troubleshooting#
| Problem | Solution |
|---|---|
PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.cache/huggingface/hub' | The cache folder is not writable by uid 1000: sudo chown -R 1000:1000 ~/docker_volumes/speaches/hf-cache |
could not select device driver "nvidia" with capabilities: [[gpu]] | Install the NVIDIA Container Toolkit and restart Docker, or use the CPU image |
| 401 or 403 from every request | The key is wrong or missing. Send Authorization: Bearer <key> |
Failed to decode audio. The provided file type is not supported. (415) | Convert the file to wav or mp3 and try again |
422 on POST /v1/models/<id> | Known bug (#655). Use speaches-cli model download <id> |
| 500 on a silent or empty file | Known bug (#667, divide by zero). Do not send empty audio |
| Slow first request | The model loads on the first call. Download it ahead (Step 3) and set WHISPER__TTL=-1 to keep it loaded |
| Open WebUI says it cannot transcribe | Check the base URL ends in /v1, the key matches, both containers share a network, and the model is downloaded |
Source install errors (not the Docker image)#
If you run Speaches from source instead of Docker, you may see these. They are known issues and the Docker image avoids them:
ModuleNotFoundError: No module named 'gradio'(#671): runuv sync --extra ui.ChatInterface.__init__() got an unexpected keyword argument 'type'(#678): gradio 6 is too new. Pingradio<6.
Known bugs#
- Unloading a model does not always give memory back (#638, #681).
- Chinese TTS fails (#647).
- Parakeet model downloads can be incomplete (#657).
- CUDA does not work on ARM64 images (#620).
Diagnostics: docker logs speaches --tail 100, docker compose ps, docker inspect speaches.
Updates and backups#
Change the pinned tag, pull, restart. Back up your config; the models can be downloaded again.
Update: read the release notes at https://github.com/speaches-ai/speaches/releases, change the tag in compose.yaml, then:
cd ~/docker_volumes/speaches && docker compose pull && docker compose up -d
Set-Location "$HOME\docker_volumes\speaches"; docker compose pull; docker compose up -d
Backup script (Linux and Mac), saved as ~/docker_volumes/speaches/backup.sh. It skips the big model cache and keeps your config:
#!/usr/bin/env bash set -e cd ~/docker_volumes mkdir -p speaches/backups tar czf speaches/backups/speaches-$(date +%F).tgz --exclude=speaches/hf-cache --exclude=speaches/backups speaches
Schedule it at 03:00 with crontab -e: 0 3 * * * bash ~/docker_volumes/speaches/backup.sh. On Windows, use Task Scheduler with this PowerShell line:
Compress-Archive -Path "$HOME\docker_volumes\speaches\.env","$HOME\docker_volumes\speaches\compose.yaml" -DestinationPath "$HOME\docker_volumes\speaches\backups\speaches-$(Get-Date -Format yyyy-MM-dd).zip" -Force
Create the backups folder first on Windows (New-Item -ItemType Directory -Force "$HOME\docker_volumes\speaches\backups").
Restore test (Linux and Mac): stop the original so port 8000 is free, then unpack into a scratch folder and start it.
cd ~/docker_volumes/speaches && docker compose down mkdir -p /tmp/restore && tar xzf backups/speaches-*.tgz -C /tmp/restore cd /tmp/restore/speaches && mkdir -p hf-cache && docker compose up -d curl -s http://localhost:8000/health
The restored server has no models yet. Download one again (Step 3) and transcribe a short file to prove it works. Then docker compose down there and start your real one again.
Performance and resources#
Use a GPU for anything bigger than small, and int8 on a CPU.
- Watch it with
docker stats. WHISPER__COMPUTE_TYPE=int8shrinks memory on a CPU.WHISPER__INFERENCE_DEVICEpicks the device (note the double underscore).WHISPER__TTLis how many seconds an idle model stays loaded (default 300).-1keeps it loaded for the lowest delay.LOG_LEVEL=infoquiets the default debug logs.- The voice-activity filter is on by default and technically differs from the OpenAI spec. This helps with silence, but check it if you compare results with OpenAI.
- faster-whisper itself is much faster than the original Whisper: its release notes mention 4x faster batched inference in v1.1.0.
FAQ#
Should I use the release candidate or the stable release? Use v0.8.3 unless you need diarization. This guide pins it for that reason.
Does it work on a Mac GPU? Not inside Docker. Use the CPU image. v0.8.2 added an Apple Silicon speedup, but how much it helps in a container is unclear (VERIFY).
Why does Open WebUI ask for a key? Open WebUI needs a non-empty value. Speaches only checks it if you set API_KEY, and this guide does.
Is the project still maintained? Issues #659 and #622 ask exactly that, and the last commit we saw is from April 2026. It works today. Keep your own backups and pin versions.
Which model for Japanese? Try Systran/faster-whisper-large-v3 with language=ja on a GPU, or Systran/faster-whisper-small on a CPU. Avoid the English-only distil models.
Sources#
Read on 2026-10-26:
- Speaches README
- Speaches docs: installation, speech-to-text, text-to-speech, Open WebUI
- Speaches releases (v0.8.3, v0.9.0-rc.3)
- Speaches issues (#620 to #681 cited above)
- Image registry (tags checked)
- faster-whisper
- OpenAI audio API reference
Checked against the official docs and the v0.8.3 source on 2026-10-26. The commands were not run on a machine for this guide.