DockerSelf-HostingAI

Kokoro-FastAPI with Docker: A Local OpenAI-Compatible Text-to-Speech Server (Linux, Windows, Mac)

Before you start

  • 20 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#

Kokoro-FastAPI is a free Docker wrapper that turns the small Kokoro-82M voice model into a text-to-speech server with the same speech API as OpenAI, so your text is read aloud on your own machine and never sent to a cloud service.

Kokoro-FastAPI official logo banner (FastKoko)

Logo: the official Kokoro-FastAPI project, README header image (github.com/remsky/Kokoro-FastAPI). The project has no separate logo file.

You point any tool that speaks the OpenAI speech API at http://<your-server>:8880/v1, and it gets spoken audio back. The container also serves a small web player and a Swagger page. People use it for read-aloud in Open WebUI, audiobooks and captions with word timings. The README says the images come with the model baked in, so there is no separate model download.

WhatValue (official docs, read October 2026)
Current versionKokoro-FastAPI v0.9.2, released October 2026 (latest release)
Images to pinghcr.io/remsky/kokoro-fastapi-cpu:v0.9.2 and ghcr.io/remsky/kokoro-fastapi-gpu:v0.9.2
Time neededAbout 15 minutes (our estimate, not measured)
Image sizeCPU about 1.5 GB, GPU about 4.6 GB compressed (registry data, amd64)
RAMNot documented for CPU; the README shows 3.1 to 4.0 GB of GPU memory in use
PortTCP 8880 (API at /v1, web player at /web, docs at /docs)
Code licenceApache-2.0 (LICENSE at the release tag)
Model licenceKokoro-82M weights: Apache-2.0 (model card on Hugging Face)
Official docsthe repository README and its docs folder

At a glance:

  • What it is: one container with an OpenAI-style /v1/audio/speech endpoint, a web player, voice mixing and caption timings.
  • CPU or GPU: the CPU image runs anywhere Docker runs. The NVIDIA GPU image is for Linux and for Windows with WSL 2. Mac uses the CPU image.
  • No login: the API has no API key or password. Keep it on 127.0.0.1 or behind a proxy that asks for one.
  • Pin the tag: the README says :latest exists but asks you to pin a release tag.
  • Languages: English (US and UK), Spanish, French, Hindi, Italian, Japanese, Brazilian Portuguese and Mandarin Chinese.
  • Related guides: this is not the project from our older Kokoro Web guide. For speech-to-text see Speaches; for the chat page see Open WebUI with Ollama.

How it fits together#

Apps send text and a voice name to one API port. The container cleans and splits the text, the baked-in Kokoro model speaks it, and audio comes back.

Diagram: Open WebUI or your own script sends text and a voice to the Kokoro-FastAPI container on port 8880, optionally through Traefik or Caddy over HTTPS; the container cleans and splits the text, the baked-in Kokoro-82M model turns it into speech, and an audio file or stream comes back

Drawn for this guide from the official README and configuration docs. The dashed HTTPS route is optional.

Before you start#

CPU imageNVIDIA GPU image
Runs onLinux, Windows (Docker Desktop), Mac (Docker Desktop); amd64 and arm64Linux (amd64, arm64), Windows with WSL 2
HardwareAny recent CPU; RAM not documentedNVIDIA card; GTX 900 series to RTX 40 use the default tag, RTX 50 needs the -cu128 tag
SpeedREADME: about 3.5 s to first audio on an older i7, under 1 s on an M3 ProREADME: about 0.3 s to first audio and 35 to 100 times faster than real time on an RTX 4060 Ti
Extra softwareDockerDocker, NVIDIA driver, NVIDIA Container Toolkit (Linux)

AMD GPUs have an experimental ROCm image that needs a native Linux host (not Docker Desktop or WSL 2); this guide does not cover it. Apple GPU speed (MPS) exists only by running the project directly with uv and start-gpu_mac.sh, not in Docker. We did not test that.

Check Docker on every system:

Run in terminalbash
docker --version
docker compose version

On Windows, open PowerShell and run the same two commands. For the GPU image on Linux, nvidia-smi must list your card; on Windows, Docker's docs ask for current NVIDIA drivers, Docker Desktop with the WSL 2 backend, and wsl --update.

Checklist:

  • [ ] Docker and Docker Compose work (docker compose version prints a version)
  • [ ] Port 8880 is free on your computer
  • [ ] Free disk space well above the compressed image sizes (CPU 1.5 GB, GPU 4.6 GB); we did not measure the unpacked size
  • [ ] GPU users: the card shows in nvidia-smi, and the NVIDIA Container Toolkit is installed on Linux
  • [ ] For the HTTPS section only: a domain name, a DNS record to your server, and ports 80 and 443 free

Tested by DIY Lab Hub: not run. Every command comes from the official README, docs, Dockerfiles and compose files read in October 2026.

Step 1: Create the folder and the Docker network#

Kokoro-FastAPI keeps no data of its own, because the model and the voices are inside the image. We still use ~/docker_volumes/kokoro-fastapi for the Compose file and its backups. A shared network lets Open WebUI and a proxy reach the container by name.

Linux#

Run in terminalbash
mkdir -p ~/docker_volumes/kokoro-fastapi/backups
cd ~/docker_volumes/kokoro-fastapi
docker network create kokoro-net

Windows (PowerShell)#

Codepowershell
New-Item -ItemType Directory -Force "$HOME\docker_volumes\kokoro-fastapi\backups" | Out-Null
Set-Location "$HOME\docker_volumes\kokoro-fastapi"
docker network create kokoro-net

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
mkdir -p ~/docker_volumes/kokoro-fastapi/backups
cd ~/docker_volumes/kokoro-fastapi
docker network create kokoro-net

Check it worked. docker network ls lists kokoro-net.

Tip: Do not mount folders into the container. The model and voice packs are baked into the image, and a mount over /app/api hides them (see Common mistakes).

Step 2: Write the Compose file (CPU or GPU)#

This file is based on the project's own compose files, with four changes: the published image pinned to v0.9.2, no source mounts, the port bound to 127.0.0.1, and the shared network. The health check is ours: curl is installed in the image and /health exists in the API code.

Linux#

CPU version:

Run in terminalbash
cd ~/docker_volumes/kokoro-fastapi
cat > docker-compose.yml << 'EOF'
name: kokoro-fastapi
services:
  kokoro-fastapi:
    image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.9.2
    container_name: kokoro-fastapi
    restart: unless-stopped
    ports:
      - "127.0.0.1:8880:8880"
    environment:
      - API_LOG_LEVEL=INFO
    networks:
      - kokoro-net
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8880/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s

networks:
  kokoro-net:
    external: true
EOF

NVIDIA GPU version: change the image line to ghcr.io/remsky/kokoro-fastapi-gpu:v0.9.2 (use v0.9.2-cu128 for an RTX 50 card) and add this block under the service, copied from the project's GPU compose file:

File contentyaml
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

On Linux the GPU also needs the NVIDIA Container Toolkit. After installing it from NVIDIA's guide, run the two commands from that guide:

Run in terminalbash
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

Windows (PowerShell)#

CPU version (a here-string keeps the file exact):

Codepowershell
Set-Location "$HOME\docker_volumes\kokoro-fastapi"
@'
name: kokoro-fastapi
services:
  kokoro-fastapi:
    image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.9.2
    container_name: kokoro-fastapi
    restart: unless-stopped
    ports:
      - "127.0.0.1:8880:8880"
    environment:
      - API_LOG_LEVEL=INFO
    networks:
      - kokoro-net
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:8880/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s

networks:
  kokoro-net:
    external: true
'@ | Set-Content -Encoding ascii docker-compose.yml

For the GPU version, make the same two changes as on Linux (GPU image, deploy block). Docker Desktop needs the WSL 2 backend, an NVIDIA card and current NVIDIA drivers; Docker's GPU page lists no toolkit step for Windows.

Mac (Terminal)#

Same commands as Linux, CPU version only. The README says the Docker GPU image is CUDA-only and does not run on Apple Silicon, so use kokoro-fastapi-cpu.

Check it worked. docker compose config prints the file without errors.

Potential issue: if port 8880 is taken, change the left number only, for example 127.0.0.1:8881:8880.

Step 3: Start it and check it#

The first start pulls the image, which is the slow part.

Linux#

Run in terminalbash
cd ~/docker_volumes/kokoro-fastapi
docker compose up -d
docker compose ps
docker compose logs --tail=40 kokoro-fastapi
curl -s http://localhost:8880/health

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\kokoro-fastapi"
docker compose up -d
docker compose ps
docker compose logs --tail=40 kokoro-fastapi
Invoke-RestMethod http://localhost:8880/health

Mac (Terminal)#

Same commands as Linux.

Check it worked. The health answer is {"status":"healthy"} (from the project's source code). The log banner shows Model warmed up on cpu, Running on CPU and the number of voice packs; the GPU image shows CUDA: True. After about a minute docker compose ps should report healthy. Open http://localhost:8880/web for the player and /docs for the API pages.

Tip: On the GPU image, read the banner. CUDA: False means the container does not see your card, even if the log also shows a harmless Can't initialize NVML warning (issue 527).

Step 4: Make your first speech file#

Linux#

Run in terminalbash
curl -s http://localhost:8880/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"model":"kokoro","input":"Hello from my own server.","voice":"af_heart","response_format":"mp3"}' \
  --output hello.mp3
ls -l hello.mp3

Play hello.mp3 with your usual audio player.

Windows (PowerShell)#

Codepowershell
$body = @{ model = "kokoro"; input = "Hello from my own server."; voice = "af_heart"; response_format = "mp3" } | ConvertTo-Json
Invoke-WebRequest -Uri http://localhost:8880/v1/audio/speech -Method Post -ContentType "application/json" -Body $body -OutFile hello.mp3
Get-Item hello.mp3
Start-Process .\hello.mp3

Mac (Terminal)#

Same request as Linux, then play it:

Run in terminalbash
curl -s http://localhost:8880/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"model":"kokoro","input":"Hello from my own server.","voice":"af_heart","response_format":"mp3"}' \
  --output hello.mp3
afplay hello.mp3

Check it worked. hello.mp3 is larger than zero bytes and plays a spoken sentence. af_heart is the project's default voice. The model names kokoro, tts-1, tts-1-hd and gpt-4o-mini-tts all point to the same Kokoro model.

Tip: The Python snippet in the README uses the official openai library with base_url="http://localhost:8880/v1" and api_key="not-needed". Any non-empty key works because the server does not check it.

Voices, speed and formats#

SettingHowNotes
Voice"voice": "af_bella"List them with GET /v1/audio/voices. The first letter is the language, the second the gender.
Mix voices"voice": "af_bella(2)+af_sky(1)"Weights are normalised to a 67 percent and 33 percent mix.
OpenAI voice namesalloy, echo, nova, shimmer, and othersMapped to Kokoro voices in api/src/core/openai_mappings.json. For example alloy becomes am_adam.
Speed"speed": 1.2Per request.
Format"response_format"mp3, wav, opus, flac, aac or pcm.
Pause[pause:1.5s] inside the textExactly this form; [pause=1.5] is read aloud.

The README warns that text cleanup can drop or change some phrases. Add "normalization_options":{"normalize": false} to the request to send your text as written.

Step 5: Connect Open WebUI#

Open WebUI can read answers aloud through any OpenAI-style speech server. Its docs have a Kokoro-FastAPI page, but it is a community contribution (not supported by the Open WebUI team) and still shows an old v0.2.1 tag. Use its settings with the tag from this guide.

Linux#

Join Open WebUI to the same network. In the compose file from our Open WebUI guide, merge these lines into the existing open-webui service and networks: block (keep default so it still reaches Ollama):

File contentyaml
services:
  open-webui:
    networks: [default, kokoro-net]

networks:
  kokoro-net:
    external: true

Apply it with docker compose up -d in that folder. Then open Open WebUI, click your profile picture, choose Admin Panel, Settings, Audio, and set:

FieldValue
Text-to-Speech EngineOpenAI
API Base URLhttp://kokoro-fastapi:8880/v1
API Keynot-needed
TTS Modelkokoro
TTS Voiceaf_heart

Click Save. If Open WebUI runs directly on the host (not in Docker), use http://localhost:8880/v1.

Windows (PowerShell)#

Same compose change, same Admin Panel settings. Docker Desktop runs both containers, so the name kokoro-fastapi works on the shared network. For Open WebUI outside Docker, use http://localhost:8880/v1.

Mac (Terminal)#

Same as Windows. Docker Desktop and the shared network behave the same way.

Check it worked. In a chat, use the Read Aloud (speaker) button under an answer: you hear the voice you chose. In docker compose logs kokoro-fastapi --tail=20 a POST /v1/audio/speech line appears.

Tip: localhost inside the Open WebUI container is Open WebUI itself. The project's troubleshooting page lists host.docker.internal for container-to-host traffic, but with the port bound to 127.0.0.1 the shared network is the reliable way.

Put it behind HTTPS (with or without Traefik)#

Skip this section if you only use the server on one computer. Use it when a phone or another computer must reach the API over the network. The API has no login, so both routes add a user name and password. Open WebUI keeps using http://kokoro-fastapi:8880/v1 inside Docker, which bypasses the proxy.

With Traefik#

On a server that already runs Traefik v3 with the Docker provider, add labels and remove the host port.

This assumes an external Docker network called proxy and an entrypoint called https (use your own names). Create the network once with docker network create proxy if it is missing. In the Compose file, delete the ports: block, add proxy to networks: (and to the bottom as external), and add these labels:

File contentyaml
    networks:
      - kokoro-net
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.kokoro.rule=Host(`tts.<your-domain>`)"
      - "traefik.http.routers.kokoro.entrypoints=https"
      - "traefik.http.routers.kokoro.tls=true"
      - "traefik.http.routers.kokoro.middlewares=kokoro-auth"
      - "traefik.http.middlewares.kokoro-auth.basicauth.users=<user>:<hash-with-every-$-doubled>"
      - "traefik.http.services.kokoro.loadbalancer.server.port=8880"

Make the hash with htpasswd -nB <user>, as Traefik's docs show. They say every $ in the hash must be doubled in a Compose file. Then run docker compose up -d.

Check it worked. curl -I https://tts.<your-domain>/health returns 401 without a password, and curl -u <user> https://tts.<your-domain>/health returns the health answer.

Without Traefik: Caddy#

Choose this if you have no Traefik. Caddy gets and renews its own certificates.

Caddy's docs describe automatic Let's Encrypt certificates for a public domain and tls internal for a local certificate at home.

Run in terminalbash
mkdir -p ~/docker_volumes/caddy/{data,config}
cd ~/docker_volumes/caddy

On Windows use New-Item -ItemType Directory -Force "$HOME\docker_volumes\caddy\data","$HOME\docker_volumes\caddy\config". Save as docker-compose.yml in that folder (image caddy:2.11.7 is the latest Caddy release read in October 2026):

File contentyaml
services:
  caddy:
    image: caddy:2.11.7
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./data:/data
      - ./config:/config
    networks:
      - kokoro-net

networks:
  kokoro-net:
    external: true

Make a password hash with Caddy's own image (it asks for the password; works on any system). Caddy's basic_auth accepts hashes only, never plain text:

Run in terminalbash
docker run --rm -it caddy:2.11.7 caddy hash-password

Save the Caddyfile next to the Compose file. For a public domain:

File contenttext
tts.example.com {
    basic_auth {
        <user> <hash-from-caddy-hash-password>
    }
    reverse_proxy kokoro-fastapi:8880
}

For a home-only name, add tls internal and use a name your own DNS or hosts file points at the server:

File contenttext
tts.home.arpa {
    tls internal
    basic_auth {
        <user> <hash-from-caddy-hash-password>
    }
    reverse_proxy kokoro-fastapi:8880
}

Start it with docker compose up -d in the Caddy folder. For the home-only name, each device must trust Caddy's own root certificate. Caddy's docs give these commands for a Caddy container (run them from the Caddy folder):

Run in terminalbash
# Linux
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /usr/local/share/ca-certificates/root.crt && sudo update-ca-certificates
# Mac
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/root.crt && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/root.crt
Run in terminalpowershell
# Windows (the official page writes %TEMP% for cmd.exe; this is the PowerShell form, in an administrator window)
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt "$env:TEMP\root.crt"
certutil -addstore -f "ROOT" "$env:TEMP\root.crt"

Firefox and Chrome may keep their own trust store, so import root.crt there too (Caddy's page lists the menu path).

Check it worked. curl -I https://tts.example.com/health shows an HTTP status over HTTPS (401 without the password). A home-only name shows a padlock only after the root is trusted on that device.

Tip: A certificate error usually means the name in the Caddyfile and the DNS record differ, or ports 80 and 443 do not reach the server.

Security and testing#

ControlHow
No built-in loginThe configuration reference lists no API key or password. Bind to 127.0.0.1 (Step 2) or add the proxy login above.
Port exposureNever forward 8880 on your router. The image and the project's native start scripts listen on all interfaces (HOST defaults to 0.0.0.0).
CORSCORS_ORIGINS defaults to ["*"]. The docs say to narrow it if the port is reachable beyond localhost.
Optional routes/debug/*, /dev/unload and the voice tuner are off by default. Leave them off unless you need them. Issue 478 reported the unload route as unauthenticated; it is now behind the ALLOW_DEV_UNLOAD flag.
Size limitsMAX_INPUT_LENGTH (1,000,000 characters) and, new in v0.9.2, MAX_REQUEST_BODY_MB (32). Lower them on a shared server.
Voice tunerOff by default. The README calls it "a tuner, not a cloner" and asks you to use only voices you have permission for.

What leaves the machine: the image pull from GHCR. The start script runs the model download script, which the docs say exits early when valid model files are baked in. We did not capture network traffic.

Quick tests after each change (Windows: use Invoke-RestMethod in PowerShell):

Run in terminalbash
cd ~/docker_volumes/kokoro-fastapi
docker compose ps
docker compose logs --tail=100 kokoro-fastapi
curl -s http://localhost:8880/health
curl -s http://localhost:8880/v1/audio/voices | head -c 300

Licence: the code and the model#

PartLicenceSource
Kokoro-FastAPI codeApache-2.0LICENSE at the v0.9.2 tag; README licence section
Kokoro-82M model weightsApache-2.0README ("The Kokoro model weights are licensed under Apache 2.0") and the Hugging Face model card (license: apache-2.0, not gated)
Inference code adapted from StyleTTS2MITREADME licence section

The model card says Kokoro was trained only on permissive or non-copyrighted audio and lists two Creative Commons BY sets (Koniwa and SIWIS) used in training. It welcomes use "from production environments to personal projects". This is our reading of the official texts, not legal advice. If you pass the software on, keep the Apache notices. The card also warns that websites with "kokoro" in the domain name are not affiliated with the model; use only the sources linked here.

Common mistakes#

These come from the project's issue tracker, its own docs, and common AI assistant answers.

MistakeWhy it happensDo this instead
Keeping the repo's ../../api:/app/api mounts with a published imageThe repo's compose file bind-mounts source for development. Issue 521 reports Could not import module "api.src.main"; issue 532 reports a missing pydubUse no mounts with image:; the docs say mounts "run published deps against local source"
Using localhost:8880 inside the Open WebUI containerlocalhost there is Open WebUI itselfJoin the shared network and use http://kokoro-fastapi:8880/v1
Changing AUDIO_TTS_* variables after Open WebUI's first startOpen WebUI stores these settings internally after the first launchChange them in Admin Panel, Settings, Audio
Publishing 8880 to the whole network or internetThe default compose maps 8880:8880, and the API has no loginPublish on 127.0.0.1; use the proxy with a password for remote use
Running :latestIt moves with every releasePin v0.9.2; to roll back, change the tag to v0.9.1
Using the default GPU tag on an RTX 50 cardThe default tag ships CUDA 12.6 libraries; Blackwell needs the -cu128 tagUse kokoro-fastapi-gpu:v0.9.2-cu128
Ignoring CUDA: False in the bannerThe GPU image falls back to the CPU quietlyCheck the banner; issue 527's fix was --group-add for the NVIDIA device group, see Troubleshooting
Expecting voice cloningThe optional tuner only moves toward a reference voicePick or mix built-in voices; enable the tuner only with permission to use the voice
AI assistants: guessing an image name such as kokoro-fastapi:latest or a Docker Hub pathThe images are kokoro-fastapi-cpu and kokoro-fastapi-gpu on ghcr.io/remskyCopy the image names from the README or this guide
AI assistants: copying the old Open WebUI page (tag v0.2.1, "CUDA 12.3", "CPU Onnx") or asking for an API keyThat page is old and the server has no keyUse v0.9.2, key not-needed, port 8880

Tip: After every compose change, run docker compose up -d again. Changing a variable in the file does nothing to a running container (issue 537 was an unrecreated container).

Tip: Test with the web player first (/web). If it speaks there but Open WebUI is silent, the problem is the connection, not the model.

Troubleshooting#

ProblemSolution
Container will not start: Error loading ASGI app. Could not import module "api.src.main"Remove any ./api:/app/api or ../../api:/app/api volume lines (issues 521, 532)
Silent or empty audio for some paragraphsA paragraph made only of symbols such as .... returns an empty file (issue 552; the fix was promised for a later minor release). Remove those lines or filter them in your app
Words missing or changedSend "normalization_options":{"normalize": false}
GPU image runs on the CPU (CUDA: False)Check nvidia-smi and the toolkit; try group_add: ["video"] in Compose (the project's GPU permissions page) or --group-add for the group of /dev/nvidiactl (issue 527)
Can't initialize NVML warningHarmless if the banner says CUDA: True (maintainer's answer in issue 527)
Open WebUI shows only six voicesFixed in the API in issue 462; update Kokoro-FastAPI and reload the Audio settings
Open WebUI cannot connectUse the container name on the shared network, and check curl http://localhost:8880/health on the host
docker compose cannot find kokoro-netRun docker network create kokoro-net once (Step 1)
WAV length looks wrong in a scriptPython's wave module misreads the streaming header; use soundfile or ffprobe (README)

Known bugs#

  • Issue 552 (closed): symbol-only paragraphs give an empty file; the maintainer said the fix would come in a later minor release, not the 0.9.2 patch.
  • Issues 551 and 535 (open): feature requests only (speed through ffmpeg, Roman numerals).

Diagnostics: docker compose ps, docker compose logs --tail=100 kokoro-fastapi, docker inspect kokoro-fastapi and docker stats kokoro-fastapi.

Updates and backups#

Update. Read the project changelog, change the tag in docker-compose.yml, then run (Windows: the same commands in PowerShell):

Run in terminalbash
cd ~/docker_volumes/kokoro-fastapi
docker compose pull
docker compose up -d
docker compose logs --tail=50 kokoro-fastapi

The README says you may have to go back to an earlier tag if something breaks. v0.9.2 is days old, so v0.9.1 is the known earlier tag.

Back up. There is no application data; back up the Compose file (and the Caddy folder if you use it):

Run in terminalbash
cd ~/docker_volumes/kokoro-fastapi
tar -czf "backups/kokoro-fastapi-$(date +%F).tar.gz" docker-compose.yml

Run it every night at 03:00 on Linux or Mac with crontab -e and this line (replace <you> with your user name):

File contenttext
0 3 * * * cd /home/<you>/docker_volumes/kokoro-fastapi && tar -czf "backups/kokoro-fastapi-$(date +\%F).tar.gz" docker-compose.yml

On Windows, save these lines as backup.ps1 in the Kokoro folder:

Codepowershell
$dir = "$HOME\docker_volumes\kokoro-fastapi"
Compress-Archive -Path "$dir\docker-compose.yml" -DestinationPath "$dir\backups\kokoro-fastapi-$(Get-Date -Format yyyy-MM-dd).zip" -Force

Then schedule it once: schtasks /Create /SC DAILY /ST 03:00 /TN "Kokoro backup" /TR "powershell.exe -NoProfile -File $HOME\docker_volumes\kokoro-fastapi\backup.ps1".

Restore test. Unpack into a new folder, never over the live one:

Run in terminalbash
mkdir -p /tmp/restore-test && tar -xzf backups/kokoro-fastapi-<date>.tar.gz -C /tmp/restore-test
cd /tmp/restore-test && docker compose config

Performance and resources#

The project publishes these numbers, measured on its author's machine (Windows 11 with WSL 2, RTX 4060 Ti 16 GB, i7-11700, 64 GB RAM). Yours will differ.

MeasureDocumented value
Speed on GPU35 to 100 times faster than real time on long books
First audio, GPUAbout 300 ms at chunk size 400
First audio, CPUAbout 3,500 ms (older i7) and under 1 s (M3 Pro), chunk size 200
GPU memory3.11 GB loaded after a 6-second job; 3.98 GB after a long job
After /dev/unload2.37 GB floor (needs ALLOW_DEV_UNLOAD=true)

Settings that matter, from the configuration reference:

  • WEB_CONCURRENCY (default 1): one worker serves one request at a time; each extra worker loads its own model copy, so GPU memory grows.
  • MODEL_AUTO_UNLOAD_TIMEOUT_SECONDS (default 0 = off): frees GPU memory after idle seconds; the next request reloads the model.
  • TARGET_MIN_TOKENS, TARGET_MAX_TOKENS, ABSOLUTE_MAX_TOKENS (175, 250, 450): how text is chunked.
  • API_LOG_LEVEL: the default is DEBUG, which is noisy; this guide sets INFO.

Watch your own numbers with docker stats kokoro-fastapi and, on NVIDIA, nvidia-smi. CPU image RAM is not documented, so measure it on a small server.

FAQ#

Is Kokoro-FastAPI free for commercial use? The code and the model weights are both Apache-2.0 according to the repository and the model card. This is our reading, not legal advice.

How is it different from our Kokoro Web guide? Kokoro Web is a different project, covered in our older guide. Kokoro-FastAPI is the remsky project in this guide, with CPU and GPU images and a documented API.

Can Speaches do the same job? Our Speaches guide covers a server that mainly does speech-to-text and also offers Kokoro for speech. Choose Kokoro-FastAPI for speech alone, with voice mixing and captions.

Does it need a GPU? No. The CPU image works everywhere; the README gives CPU first-audio times. A GPU makes long jobs much faster.

Can I run it on a Mac with Apple GPU speed? Not in Docker. The README offers ./start-gpu_mac.sh for native Apple GPU use, which we did not test. The CPU image works on Apple Silicon.

Sources#

Checked against the official docs on 2026-10-31.