Chatterbox TTS in locale (CPU/GPU) con Docker + Nginx – Guida completa

Installa e testa Chatterbox TTS in locale su Windows con Docker/WSL2: CPU o GPU, frontend Nginx, upload voce, streaming e primi test API.

Loading the

Negli ultimi mesi sto facendo un giro d’orizzonte sui Text-to-Speech locali, per capire cosa abbia senso usare “on-prem” quando privacy, latenza e costi ricorrenti contano più di tutto. In questo post metto sotto la lente Chatterbox TTS, un progetto open che espone un’API stile OpenAI e supporta la clonazione voce e lo streaming.

Sul mio hardware (Windows 11 + Docker Desktop/WSL2, RTX 3050 Ti 4 GB e 64 GB RAM) Chatterbox TTS gira bene in locale. Con GPU l’inferenza è nettamente più vivace rispetto alla CPU (che per una frase breve arrivava anche a ~60s), ma per l’italiano oggi sento ancora un accento inglese e un timbro un po’ metallico anche clonando la mia voce. Non è “pronto per doppiaggio”, però è promettente e lo terrò in test continuo.

👉Valutazione e pro/contro qui

Perché TTS locale

  • Privacy: gli audio non escono dalla tua macchina.
  • Predictable cost: niente token/overage.
  • Bassa latenza (specie su GPU) + controllo totale del deployment.

Cosa è Chatterbox TTS (in 20 secondi)

  • Server FastAPI con documentazione /docs e schema /openapi.json.
  • Endpoint compatibili con OpenAI per /v1/audio/speech e vari alias (/audio/speech, /audio/speech/stream).
  • Modello multilingua con 20+ lingue (italiano incluso).
  • Voice library integrata: puoi caricare campioni WAV/MP3, rinominare, impostare una default voice, gestire alias, scaricare/eliminare.
  • Modalità streaming per sentire l’audio mentre viene generato.

La mia postazione di test

  • OS: Windows 11 + Docker Desktop (con WSL2)
  • GPU: NVIDIA RTX 3050 Ti 4 GB
  • RAM: 64 GB
  • Container:
    • chatterbox-tts-api-cpu (porta 4123)
    • chatterbox-tts-api-gpu (sempre 4123, device cuda)
    • Frontend statico su Nginx (porta 8080) con VITE_API_BASE puntato all’API

Nota: per chi usa Docker su Windows, l’API gira dentro docker-desktop (WSL2). Il processo VmmemWSL può salire di RAM: è normale durante il primo warm-up modello.

Cosa ho testato

  • Lingue disponibili via GET /languages → 22 (italiano incluso).
  • Voci personali caricate via POST /voices (es. italian_sample.wav) e verificate con GET /voices.
  • Generazione non-streaming su /audio/speech e streaming su /audio/speech/stream con chunking per frase.
  • Frontend Docker con Nginx davanti: pulsante “Generate”, pannello “Advanced Settings”.

Tuning che mi ha convinto di più (italiano)

Nella UI, questi range hanno dato i risultati più naturali:

  • Exaggeration (emotività): 0.35–0.50
  • Pace / CFG Weight (ritmo/pausa): 0.25–0.40
  • Temperature (variazione/creatività): 0.60–0.80
  • Streaming: strategy sentence, chunk ~frase

Se senti “sibilanti” o cantilena inglese, abbassa un filo Exaggeration e Temperature, e tieni CFG sotto 0.4.

Qualità della voce in italiano

La parte più importante. Con campioni voce personali puliti (WAV 16-48 kHz, 16-bit, senza musica/riverbero, 30–60 s), la somiglianza timbrica c’è, ma:

  • rimane una patina anglofona (intonazione e /r/ /t/ /s/ spesso “anglicizzate”);
  • in certe frasi si sente una leggerissima metallicità;
  • la prosodia non sempre “aggancia” le virgole.

Non è un problema di pipeline: è proprio lo stato attuale del modello multilingua. Se il tuo target è assistenti vocali, demo interne o prototipi va già bene; per voice-over pubblicitari o doppiaggio serve ancora un salto.

Come migliorare la resa (consigli pratici)

  1. Campione voce: 30–60 s, ambiente trattato, microfono decente, ritmo naturale.
  2. Testo campione: includi accenti, apostrofi, numeri, nomi propri italiani.
  3. Pronuncia: evita inflessioni teatrali; meglio neutra e scorrevole.
  4. Parametri: parti dai range sopra, poi micro-tuning su frasi difficili.
  5. Warm-up: la prima generazione scalda cache e può essere lenta; le successive migliorano.

Se ti serve, ho preparato un copione sample per caricare una voce “ricca” di fonemi italiani (lo trovi nella guida).

Performance: CPU vs GPU

  • CPU: una frase breve può arrivare anche a ~60 secondi la prima volta (poi migliora, ma resta poco reattivo).
  • GPU (RTX 3050 Ti 4 GB): inferenza molto più fluida; non è “istantanea” come i TTS nativi-inglese più ottimizzati, ma è usabile e con streaming l’effetto percepito è decisamente migliore.
  • RAM: tener d’occhio VmmemWSL durante i primi minuti; poi si stabilizza.

Frontend & Nginx: due note che ti risparmiano ore

  • Se avvii il frontend in Docker, non usare nomi di servizio hard-codati (chatterbox-tts) nell’upstream Nginx.
    Punta l’API a http://host.docker.internal:4123 (Windows/Mac) o all’IP della macchina.
  • Nel container del frontend, passa -e VITE_API_BASE=http://host.docker.internal:4123 e usa l’Nginx già configurato per servire gli asset statici.

Problemi incontrati (e soluzioni lampo)

  • PowerShell vs bash: i comandi curl con -H e -d danno errore in PS se copiati “alla Linux”.
    Usa curl.exe e json con --data/-d tra apici doppi, oppure fai le chiamate con Python/requests.
  • Frontend che “loopa” sulle voci: era l’upstream sbagliato in Nginx. Sistemato puntando a host.docker.internal:4123.
  • /docs ok ma frontend vuoto: quasi sempre è CORS/endpoint o variabile VITE_API_BASE mancante.

In sintesi

Chatterbox TTS è già oggi un buon candidato per chi vuole un TTS locale multilingua con una vera voice library ed endpoint comodi.
Nel mio uso reale:

  • Pro: facile da containerizzare, API chiare, streaming, voice management, gira su GPU “consumer”.
  • Contro: italiano ancora con accento straniero e prosodia da limare; CPU troppo lenta per scenari “live”.

Io lo terrò in produzione “di prova” su GPU e aggiornerò questo post man mano che escono nuovi pesi/preset.
Adesso passiamo al pezzo forte, ovvero la guida completa per rifare esattamente i miei test (CPU e GPU), con frontend Docker e Nginx già pronti, script per upload voce e chiamate streaming. Buon divertimento!

Guida pratica

Intanto se vuoi avere maggiori informazioni ti invito ad accedere alla Repo GitHub del progetto.

Iniziamo con il vero setup:

1) Prerequisiti

  • Windows 10/11 con WSL2 abilitato.
  • Docker Desktop (usa sempre la distro docker-desktop come VM dei container).
  • GPU (opzionale): driver NVIDIA recenti + “Use the WSL 2 based engine” abilitato in Docker Desktop → Settings → Resources → WSL Integration e Settings → Resources → GPU.
  • curl.exe (già presente su Windows 11), Python 3 (per gli esempi), ffmpeg opzionale se vuoi ascoltare stream al volo.

🔎 Nota WSL: le distro “Ubuntu”/“Ubuntu-22.04” sono facoltative per te. I container girano in docker-desktop.

2) Clona il progetto

cd $HOME
git clone https://github.com/travisvn/chatterbox-tts-api.git
cd chatterbox-tts-api

Struttura che ci interessa:

chatterbox-tts-api/
  docker/
    docker-compose.cpu.yml
    docker-compose.gpu.yml
  frontend/
    Dockerfile
    nginx.conf

3) Avviare il backend API

3.1 CPU (semplice e universale)

cd docker
docker compose -f docker-compose.cpu.yml up -d --build

3.2 GPU (più veloce)

cd docker
# se il container CPU è acceso, spegnilo PRIMA (stesso port 4123)
docker compose -f docker-compose.cpu.yml down

# poi avvia la versione GPU
docker compose -f docker-compose.gpu.yml up -d --build
  • Espone sempre su http://localhost:4123
  • Contenitore tipico: chatterbox-tts-api-gpu
  • Nei log vedrai Device: cuda. Puoi verificare:
docker exec -it chatterbox-tts-api-gpu bash -lc "nvidia-smi"

3.3 Verifiche veloci

curl.exe -s http://127.0.0.1:4123/health
curl.exe -s http://127.0.0.1:4123/openapi.json > NUL
start http://127.0.0.1:4123/docs

Se /docs si apre, l’API è pronta.

3.4 Script in Python per caricare la tua voce

import requests

files = {
    "voice_file": ("italian_sample.wav", open("C:/Users/Francesco/Desktop/tts/italian_sample.wav", "rb"), "audio/wav")
}
data = {
    "voice_name": "italian_sample",
    "language": "it"
}

response = requests.post("http://localhost:4123/voices", data=data, files=files)
print(f"Upload status: {response.status_code}, response: {response.text}")

3.5 Script in Python per generare l’audio e salvarlo

import requests

url = "http://localhost:4123/audio/speech"
headers = {"accept": "audio/wav", "Content-Type": "application/json"}
data = {
    "input": "Ciao, come stai? Tutto bene? Questo è un test di generazione vocale.",
    "voice": "italian_sample",
    "response_format": "wav",
    "speed": 1.0,
    "exaggeration": 0.5,
    "cfg_weight": 0.5,
    "temperature": 0.75
}

r = requests.post(url, headers=headers, json=data)
if r.ok:
    with open("output_italian.wav", "wb") as f:
        f.write(r.content)
    print("Audio salvato come output_italian.wav")
else:
    print(r.status_code, r.text)

4) Avviare il frontend con Docker

Costruiamo l’immagine e lanciamo un container Nginx statico.

cd ..\frontend
docker build -t chatterbox-tts-frontend .

4.1 Modalità “standalone” (frontend separato dall’API)

In Windows, il frontend deve parlare con l’API host usando host.docker.internal:

docker run --name tts-frontend --rm -p 8080:80 `
  -e VITE_API_BASE=http://host.docker.internal:4123 `
  chatterbox-tts-frontend

Apri http://localhost:8080 → ora il frontend chiama correttamente l’API.

💡 Se preferisci 127.0.0.1 al posto di host.docker.internal, modifica direttamente nginx.conf (vedi §5).

4.2 Modalità “tutto in Compose” (frontend + API nella stessa rete)

Se vuoi far parlare Nginx con l’API usando il nome del servizio (chatterbox-tts), serve un compose unico. Esempio minimale:

# docker-compose.app.yml
services:
  chatterbox-tts:
    build:
      context: ..
      dockerfile: docker/Dockerfile.cpu     # o .gpu per GPU
    image: docker-chatterbox-tts
    ports:
      - "4123:4123"

  tts-frontend:
    build:
      context: ../frontend
    image: chatterbox-tts-frontend
    ports:
      - "8080:80"
    environment:
      - VITE_API_BASE=http://chatterbox-tts:4123
    depends_on:
      - chatterbox-tts

Poi:

docker compose -f docker-compose.app.yml up -d --build

5) Nginx: configurazione pronta

5.1 Se il frontend è standalone (API fuori dal compose)

Usa questa nginx.conf (è quella che ci ha sbloccato tutto):

server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html index.htm;

    gzip on; gzip_vary on; gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;

    # API Proxy → backend locale su Windows/Mac:
    set $api http://host.docker.internal:4123;

    location /v1/          { proxy_pass $api; }
    location /health       { proxy_pass $api; }
    location /config       { proxy_pass $api; }
    location /models       { proxy_pass $api; }
    location /memory       { proxy_pass $api; }
    location /docs         { proxy_pass $api; }
    location /redoc        { proxy_pass $api; }
    location /openapi.json { proxy_pass $api; }

    # Static cache
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
    }

    # SPA routing
    location / {
        try_files $uri $uri/ /index.html;
        add_header X-Content-Type-Options nosniff;
        add_header X-Frame-Options DENY;
        add_header X-XSS-Protection "1; mode=block";
    }

    # Health del frontend
    location /frontend-health { access_log off; return 200 "frontend healthy\n"; }

    # Sicurezza di base
    add_header Referrer-Policy "strict-origin-when-cross-origin";
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' http://host.docker.internal:4123 ws://localhost:*";

    server_tokens off;
    client_max_body_size 10M;
}

Se preferisci 127.0.0.1, cambia set $api http://127.0.0.1:4123;.

5.2 Se usi compose per entrambi

Nel nginx.conf metti:

set $api http://chatterbox-tts:4123;

perché i servizi condividono la rete Compose.

6) Primi test API (PowerShell-friendly)

⚠️ In PowerShell usa curl.exe (non l’alias curl) per evitare i classici errori di parametri.

6.1 Lingue e voci

curl.exe -s http://127.0.0.1:4123/languages
curl.exe -s http://127.0.0.1:4123/voices

6.2 Carica una voce (clonazione semplice)

File di esempio: C:\Users\Francesco\Desktop\tts\italian_sample.wav (16 kHz/24 kHz, mono, 5–20 s, pulito).

curl.exe -s -X POST http://127.0.0.1:4123/voices `
  --form "voice_name=italian_sample" `
  --form "language=it" `
  --form "voice_file=@C:\Users\Francesco\Desktop\tts\italian_sample.wav"

Verifica:

curl.exe -s http://127.0.0.1:4123/voices

6.3 Genera audio (WAV) – endpoint compatto

curl.exe -s -X POST http://127.0.0.1:4123/audio/speech `
  --json "{`"input`": `"Ciao, come stai? Tutto bene?`", `"voice`": `"italian_sample`", `"language_id`": `"it`", `"exaggeration`": 0.4, `"cfg_weight`": 0.3, `"temperature`": 0.6}" `
  --output out.wav

Apri out.wav e ascolta.

6.4 Streaming “a spezzoni” (Python, salva mentre arriva)

import requests

URL = "http://127.0.0.1:4123/audio/speech/stream"
payload = {
    "input": "Ciao! Prova di streaming in tempo reale.",
    "voice": "italian_sample",
    "language_id": "it",
    "streaming_strategy": "sentence",
    "exaggeration": 0.4,
    "cfg_weight": 0.3,
    "temperature": 0.6
}

with requests.post(URL, json=payload, stream=True, timeout=120) as r:
    r.raise_for_status()
    with open("stream.wav", "wb") as f:
        for chunk in r.iter_content(8192):
            if chunk:
                f.write(chunk)

print("OK -> stream.wav")

7) Impostazioni consigliate (italiano)

  • Campione voce: 5–20 s, mono, 16 kHz o 24 kHz, senza rumore/sottofondo, tono colloquiale.
  • Parametri “Advanced”
    • language_id: “it”
    • cfg_weight (Pace): 0.3–0.45
    • exaggeration (Emozione): 0.35–0.6
    • temperature (creatività): 0.55–0.8
  • Per voci “piatte” o con accento inglese: carica un campione più pulito e rallenta un filo il pace (cfg_weight ~0.35).

8) Operazioni utili

  • Imposta la voce di default curl.exe -s -X POST http://127.0.0.1:4123/voices/default -d "voice_name=italian_sample" curl.exe -s http://127.0.0.1:4123/voices/default
  • Pulizia/diagnostica memoria curl.exe -s "http://127.0.0.1:4123/memory?cleanup=true&force_cuda_clear=true"
  • Stato/Statistiche curl.exe -s http://127.0.0.1:4123/status curl.exe -s http://127.0.0.1:4123/status/statistics

9) Troubleshooting (problemi visti davvero)

  • Frontend “non trova le voci” / loop richieste
    → Manca/è sbagliato l’endpoint API.
    Se è standalone: lancia con -e VITE_API_BASE=http://host.docker.internal:4123 oppure modifica nginx.conf come in §5.1.
    In alternativa puoi cambiarlo facilmente dal frontend.
  • Errore Nginx “host not found in upstream ‘chatterbox-tts’”
    → Capitava quando il frontend era standalone ma il proxy puntava al nome compose.
    Usa host.docker.internal (Windows/Mac) o 127.0.0.1.
  • PowerShell e curl che “non capisce” -H o -d
    → In PS l’alias curl è Invoke-WebRequest. Usa curl.exe.
  • CPU e GPU insieme sullo stesso 4123
    → Non puoi pubblicare lo stesso port due volte. Spegni l’altro:
    docker compose -f docker-compose.cpu.yml down
  • GPU non usata
    → Controlla i log dell’API (Device: cuda) e docker exec ... nvidia-smi. Abilita GPU in Docker Desktop.
  • IPv6/localhost “muto”
    → Usa http://127.0.0.1:4123 per i test curl.

10) Pulizia/Disinstallazione

Spegni i container:

# backend (CPU o GPU)
cd chatterbox-tts-api\docker
docker compose -f docker-compose.cpu.yml down
docker compose -f docker-compose.gpu.yml down

# frontend
docker rm -f tts-frontend

Rimuovi immagini (facoltativo):

docker rmi docker-chatterbox-tts chatterbox-tts-frontend

Volumi (attenzione: perdi voci caricate e cache modelli):

docker volume rm docker_chatterbox-voices docker_chatterbox-models

11) Esempio “ready-to-run” (CPU) con tutto in Compose

Salva come docker-compose.all.yml nella cartella docker/:

services:
  chatterbox-tts:
    build:
      context: ..
      dockerfile: docker/Dockerfile.cpu
    image: docker-chatterbox-tts
    ports:
      - "4123:4123"
    volumes:
      - chatterbox-models:/cache
      - chatterbox-voices:/voices

  tts-frontend:
    build:
      context: ../frontend
    image: chatterbox-tts-frontend
    ports:
      - "8080:80"
    environment:
      - VITE_API_BASE=http://chatterbox-tts:4123
    depends_on:
      - chatterbox-tts

volumes:
  chatterbox-models:
  chatterbox-voices:

Lancio:

cd chatterbox-tts-api\docker
docker compose -f docker-compose.all.yml up -d --build
start http://localhost:8080

12) FAQ lampo

  • Posso usare solo l’API senza frontend? Certo, lavori da /docs o con script.
  • Perché vedo tanta RAM su “VmmemWSL”? È la VM WSL che contiene docker-desktop; è normale (cache).
    Puoi limitare risorse con un .wslconfig se vuoi.
  • In locale Chatterbox TTS è tra i motori più semplici da mettere in piedi e integrare via API. Per assistenti vocali, demo e POC è già usabile (soprattutto su GPU). In italiano resta un po’ di accento/prosodia da rifinire per voice-over pro. Continuerò i test e aggiornerò il post quando usciranno preset/modelli migliori.

Conclusione

In locale Chatterbox TTS è tra i motori più semplici da mettere in piedi e integrare via API. Per assistenti vocali, demo e POC è già usabile (soprattutto su GPU). In italiano resta un po’ di accento/prosodia da rifinire per voice-over pro. Continuerò i test e aggiornerò il post quando usciranno preset/modelli migliori.

Spero che questa guida ti sia stata utile, se hai dubbi e domande o semplicemente vuoi condividere il tuo feedback ti invito a scrivere un commento qui sotto.

Francesco Gruner
Francesco Gruner

Sono un consulente IT, divulgatore e imprenditore tech. Mi occupo di automazione, AI e gestione di sistemi e infrastrutture IT, cercando soluzioni semplici a problemi complessi. Qui condivido strumenti, esperimenti e idee utili.

Resta aggiornato su AI e Automazione

Un recap con novità sull’AI, casi reali e strumenti che uso ogni giorno.

Lascia una risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *