엘레븐랩스 같은 상용 TTS는 훌륭하지만, 사용량이 늘면 비용이 부담스럽고 데이터가 외부로 나가는 것도 신경 쓰인다. 그래서 맥미니를 개인 서버로 활용해 오픈소스 TTS를 도커로 셀프호스팅해 보기로 했다. 목표는 명확했다 — 한국어가 되고, 도커로 관리하며, 나중에 n8n 워크플로우에서 HTTP로 호출할 수 있는 서버.
이 글은 그 구축 과정을 처음부터 그대로 따라 하면 성공하도록 정리한 것이다. CPU 환경 특유의 함정이 하나 있는데, 본문 흐름을 끊지 않도록 맨 아래 트러블슈팅 섹션으로 따로 빼두었다.
다음 글에서는 이렇게 만든 TTS 서버를 n8n 워크플로우에 연동하는 방법을 다룬다.
설치 경로는 /Volumes/docker/chatterbox-tts-server를 예시로 각자 경로에 맞게 진행
왜 Chatterbox인가
한국어 TTS를 셀프호스팅하려고 오픈소스 모델을 여럿 비교했다. 결론부터 말하면, "엘레븐랩스 대체 오픈소스"로 가장 유명한 모델들이 정작 한국어에는 약하다는 게 첫 번째 함정이었다.
- Kokoro — 가볍고 빠르기로 가장 인기 있지만 영어 중심이라 한국어 지원이 사실상 없다.
- Chatterbox 원본 — 블라인드 테스트에서 엘레븐랩스를 이겼다고 가장 많이 인용되지만, 원본은 영어 전용이다.
다행히 Resemble AI가 내놓은 Chatterbox Multilingual은 한국어를 포함한 23개 언어를 지원하고, MIT 라이선스라 상업적 사용도 가능하며, 음성 클로닝까지 된다. 모델 자체는 Resemble AI 공식이고, 이걸 도커 + HTTP API + 웹 UI로 감싼 서드파티 서버가 바로 devnen/Chatterbox-TTS-Server다. 셀프호스팅에 필요한 도커 구성과 OpenAI 호환 API가 이미 다 갖춰져 있어서 이 프로젝트를 쓰기로 했다.
환경
- 맥미니 (Apple Silicon), 개인 서버로 사용
- 도커로 구동 (n8n, whisper 등 다른 컨테이너와 함께 운영)
- CPU 추론 — 맥에서 도커는 리눅스 VM 안에서 돌기 때문에, Apple Silicon이라도 컨테이너 안에서는 GPU(Metal)를 쓸 수 없다. 즉 도커로 가는 이상 CPU 전용이다. 실시간 스트리밍은 포기하고, 미리 음성을 생성해 두는 배치 용도로 쓴다.
CPU 추론이라 음성 생성에 시간이 걸린다. 짧은 문장도 수십 초 단위일 수 있으니, 실시간 응답이 필요한 용도에는 맞지 않는다.
1단계 — 리포 클론
이 프로젝트는 사전 빌드된 도커 이미지를 배포하지 않는다. 소스에서 직접 빌드해야 하므로, 먼저 도커 컴포즈 파일이 있는 디렉터리에서 리포를 클론한다.
cd /Volumes/docker
git clone https://github.com/devnen/Chatterbox-TTS-Server chatterbox-tts-server
클론하면 chatterbox-tts-server 폴더가 생기고, 그 안에 Dockerfile.cpu, config.yaml 등이 들어온다.
2단계 — docker-compose.yml에 서비스 추가
기존 컴포즈의 services: 블록에 chatterbox 서비스를 추가한다. CPU 빌드용 Dockerfile.cpu를 사용한다.
chatterbox:
build:
context: ./chatterbox-tts-server
dockerfile: Dockerfile.cpu
container_name: chatterbox
restart: unless-stopped
ports:
- "8004:8004"
volumes:
# 설정·보이스·레퍼런스 오디오·로그 영속화
- ./chatterbox-tts-server/config.yaml:/app/config.yaml
- ./chatterbox-tts-server/voices:/app/voices
- ./chatterbox-tts-server/reference_audio:/app/reference_audio
- ./chatterbox-tts-server/logs:/app/logs
# 생성된 오디오를 공유 스토리지로 (다른 컨테이너가 바로 읽도록)
- /Volumes/docker/storage/tts:/app/outputs
# HF 모델 캐시 (수 GB). 컨테이너를 다시 만들어도 모델 재다운로드 방지
- ./chatterbox-tts-server/hf_cache:/app/hf_cache
environment:
- TZ=Asia/Seoul
- HF_HUB_ENABLE_HF_TRANSFER=1
shm_size: "1gb"
deploy:
resources:
limits:
memory: 6G
cpus: "4.0"
맥용 Docker Desktop은 자체 VM 안에서 돌고, VM에 할당된 RAM이 컨테이너 상한이다. 위
memory: 6G보다 VM 메모리가 작으면 OOM으로 죽으니, Docker Desktop 설정 → Resources에서 VM 메모리가 충분한지(최소 8GB 권장) 먼저 확인하자.
3단계 — 한국어 모델 지정
config.yaml을 열어 모델을 멀티링구얼로, 디바이스를 CPU로 설정한다.
model:
repo_id: chatterbox-multilingual # 한국어 포함 23개 언어
tts_engine:
device: cpu
repo_id 선택지는 세 가지다.
chatterbox(또는original) — 영어 전용chatterbox-turbo(또는turbo) — 350M 경량, 빠름 (영어 중심)chatterbox-multilingual(또는multilingual) — 한국어 포함 23개 언어 ← 우리가 쓸 것
웹 UI의 엔진 드롭다운에서 재시작 없이 핫스왑도 가능하다.
4단계 — 빌드 & 기동
cd /Volumes/docker
docker compose up -d --build chatterbox
docker compose logs -f chatterbox
첫 빌드는 의존성 컴파일 때문에 꽤 걸린다(10분 이상 가능). 빌드가 끝나면 컨테이너가 올라온다.

첫 빌드는 PyTorch 등 의존성 설치로 시간이 오래 걸린다
기동 후 로그를 보면 두 가지 경우로 갈린다.
- 정상 —
Successfully loaded ChatterboxMultilingualTTS on cpu가 뜨면 바로 다음 단계로 진행한다. - 모델 로딩 실패 — 로그에
RuntimeError: Attempting to deserialize object on a CUDA device ...에러가 나고, 웹 UI에 빨간 글씨로Model not loaded가 뜬다. 이건 CPU 환경에서 멀티링구얼 모델을 쓸 때 흔히 만나는 알려진 문제다. → 아래 [트러블슈팅] CPU에서 Multilingual 모델이 안 뜰 때 섹션을 먼저 해결하고 돌아오면 된다.
정상적으로 로드되면 웹 UI(http://localhost:8004) 상단 배지가 Multilingual로 바뀌고, 오른쪽에 초록색으로 ChatterboxMultilingualTTS loaded on cpu 가 표시된다.

멀티링구얼 모델이 CPU에 정상 로드된 상태
한국어 음성 생성해 보기
이제 텍스트 박스에 한국어를 넣고 Generate Speech를 누르면 음성이 나온다.
안녕하세요. 한국어 음성 합성 테스트입니다. 숫자도 넣어볼게요. 123456789

모델을 바꾸면 Apply & Restart로 적용한다
버튼을 누르면 "Generating audio... This may take some time" 모달이 뜨면서 생성이 시작된다.

CPU 추론이라 생성 중 대기 모달이 뜬다
생성이 끝나면 화면 아래쪽에 Generated Audio 영역이 나타나고, 파형과 함께 재생·다운로드 버튼이 생긴다. 재생해 보면 한국어가 또렷하게 들린다.

생성 완료 — 파형, 재생/다운로드 버튼, 그리고 생성 정보(Gen Time, Duration)
속도에 대한 현실적인 기대치
여기서 꼭 짚고 넘어가야 할 게 있다. 결과 정보를 보면 Gen Time: 122.85s — 46자짜리 짧은 한 문장(재생 시간 7초)을 만드는 데 약 2분이 걸렸다.
이건 버그가 아니라 CPU 추론의 본질적 한계다. GPU 없이 0.5B 모델을 돌리니 어쩔 수 없다. 그래서 이 구성은 다음과 같이 쓰는 게 맞다.
- 적합 — 영상 나레이션, 안내 음성, 오디오북처럼 미리 만들어 두는 배치 용도. 느려도 결과물 품질엔 영향 없다.
- 부적합 — "버튼 누르면 즉시 응답" 같은 실시간 용도. CPU로는 무리다.
이 소요 시간 수치는 다음 글(n8n 연동)에서 중요하다. n8n HTTP Request 노드의 타임아웃을 넉넉히(긴 텍스트는 몇 분도 가니 5분 이상) 잡지 않으면, 음성이 다 만들어지기 전에 노드가 먼저 끊겨 워크플로우가 실패한다.
속도를 정말 올리고 싶다면 도커를 벗어나 맥에 직접 설치하고 MPS(Apple Silicon GPU) 가속을 쓰는 방법이 있지만, "도커로 관리한다"는 목표를 포기해야 하는 트레이드오프가 있다.
속도 최적화 — 어디까지 가능한가
먼저 솔직하게 — CPU 환경에서 0.5B 모델을 돌리는 한, "2분 → 몇 초" 같은 극적인 단축은 불가능하다. 근본 병목은 GPU가 없다는 것이고, 아래 조정들은 어디까지나 "조금 줄이는" 수준이다. 효과가 큰 순서로 정리한다.
① 정확히 측정부터 — 2회차 시간을 본다
첫 요청의 122초에는 모델을 메모리에 올리는 시간이 섞여 있을 수 있다. 서버가 떠 있는 동안 모델은 상주하므로, 연속으로 두세 번 생성해 보고 2회차 이후 시간을 봐야 순수 생성 속도가 나온다. 최적화를 따지기 전에 이 실제 수치부터 확인하자.
② CPU 코어를 더 할당한다 (docker-compose.yml)
compose에서 cpus를 제한해 뒀는데, 맥미니 코어에 여유가 있으면 이 값을 올린다. PyTorch가 더 많은 스레드를 쓸 수 있다.
deploy:
resources:
limits:
memory: 6G
cpus: "6.0" # 코어 여유가 있으면 늘린다 (예: 4.0 → 6.0)
다만 코어를 다 몰아줘도 다른 컨테이너(n8n 등)가 굶을 수 있으니, 동시 부하를 보며 조정하자. 그리고 코어를 2배로 줘도 생성 시간이 2배 빨라지진 않는다 — 모델 추론이 완벽히 병렬화되지 않기 때문이다.
③ chunk 크기 조정 (긴 텍스트에서만 효과)
긴 글은 chunk 단위로 쪼개 순차 처리된다. chunk가 너무 작으면 청크 간 오버헤드가 누적되므로, 긴 텍스트에선 chunk 크기를 키우면(예: 240 → 350) 약간 빨라질 수 있다. 짧은 단일 문장에는 거의 영향이 없다.
④ 하지 말 것 — BF16
TTS_BF16=on 옵션이 있지만, 이건 bf16을 지원하는 GPU에서 효과를 보는 것이다. CPU에선 의미가 없거나 오히려 불안정할 수 있으니 켜지 말자.
결론 — ②③을 다 해도 체감은 "조금 빨라진" 정도다. 배치 용도라면 굳이 시간 쓸 필요 없이 2회차 시간만 확인하고 그대로 쓰는 게 실용적이다. 속도가 정말 중요하면 CPU 최적화가 아니라 환경 전환(MPS 또는 GPU 서버)이 유일하게 의미 있는 답이다.
음색에 대한 참고
기본 보이스(Emily, Elena 등)는 영어권 화자라, 한국어 발음은 나오지만 외국인이 한국어 읽는 듯한 억양이 섞일 수 있다. 자연스러운 한국어 음색을 원하면 Voice Cloning 모드에서 한국어 원어민 음성 샘플을 레퍼런스로 넣는 게 낫다. 공식 Tips에서도 _깨끗한 레퍼런스 오디오의 품질이 클로닝 결과를 좌우한다_고 안내한다.
정리
맥미니 도커에서 CPU로 한국어 TTS 서버(Chatterbox Multilingual)를 띄웠다. 핵심을 요약하면 이렇다.
- 모델 자체는 Resemble AI의 Chatterbox Multilingual (한국어 포함 23개 언어, MIT)
- 셀프호스팅은
devnen/Chatterbox-TTS-Server로 (도커 + OpenAI 호환 API + 웹 UI) - 맥 도커는 CPU 전용이라, Multilingual 모델 로딩 시 패치가 필요할 수 있다 (아래 트러블슈팅 참고)
- CPU 추론이라 느리다 — 짧은 문장 하나에 약 2분. 미리 만들어 두는 배치 용도에 적합하다
이 서버는 8004 포트에 떠 있고, /tts와 /v1/audio/speech(OpenAI 호환) 엔드포인트를 제공한다. 다음 글에서는 이 엔드포인트를 n8n 워크플로우에서 HTTP로 호출해, 한국어 음성을 자동으로 생성하고 파이프라인에 연결하는 방법을 다룬다.
[트러블슈팅] CPU에서 Multilingual 모델이 안 뜰 때
CPU 환경에서 멀티링구얼 모델을 로드하면 다음 에러로 실패할 수 있다.
RuntimeError: Attempting to deserialize object on a CUDA device but
torch.cuda.is_available() is False. If you are running on a CPU-only
machine, please use torch.load with map_location=torch.device('cpu')
웹 UI에는 빨간 글씨로 Model not loaded 가 뜬다.

패치 전 — 모델 로딩이 CUDA 에러로 실패한 상태
원인
Chatterbox 원천 모델(resemble-ai/chatterbox)의 알려진 버그다. 멀티링구얼 모델 로딩 코드(mtl_tts.py)가 가중치를 불러올 때 torch.load에 map_location을 지정하지 않아서, GPU용으로 저장된 체크포인트를 CPU에서 읽다가 터진다. 영어 모델은 패치돼 있지만 멀티링구얼 파일은 아직 안 돼 있다.
문제 라인은 mtl_tts.py의 두 곳이다(166행, 179행). 각각 torch.load(...)에 map_location이 빠져 있다.
해결 — 패치 파일을 볼륨 마운트로 덮어쓰기
이 두 줄에 map_location="cpu"를 추가하면 된다. 컨테이너 안 라이브러리 파일을 직접 고치면 재빌드 때마다 날아가므로, 고친 파일을 호스트에 두고 docker-compose 볼륨으로 덮어씌우는 방식으로 영구화한다.
1) 실행 중인 컨테이너에서 두 줄 패치
docker compose exec chatterbox sed -i \
's|"ve.pt", weights_only=True)|"ve.pt", weights_only=True, map_location="cpu")|; s|"s3gen.pt", weights_only=True)|"s3gen.pt", weights_only=True, map_location="cpu")|' \
/usr/local/lib/python3.10/site-packages/chatterbox/mtl_tts.py
2) 패치 확인
docker compose exec chatterbox grep -n "torch.load" \
/usr/local/lib/python3.10/site-packages/chatterbox/mtl_tts.py
166·179번 줄 끝의 닫는 괄호 안에 map_location="cpu"가 붙어 있으면 성공이다.
166: torch.load(ckpt_dir / "ve.pt", weights_only=True, map_location="cpu")
179: torch.load(ckpt_dir / "s3gen.pt", weights_only=True, map_location="cpu")
같은 파일 129번 줄에는 이미
map_location이 있으니 건드리지 않는다. 위 sed는 파일명(ve.pt,s3gen.pt)을 정확히 매칭하므로 129번은 영향받지 않는다.
3) 고친 파일을 호스트로 꺼내 영구 보관
cd /Volumes/docker/chatterbox-tts-server
mkdir -p patches
docker compose -f /Volumes/docker/docker-compose.yml cp \
chatterbox:/usr/local/lib/python3.10/site-packages/chatterbox/mtl_tts.py \
./patches/mtl_tts.py
4) docker-compose.yml의 chatterbox volumes: 맨 끝에 마운트 추가
volumes:
# ... 기존 볼륨들 ...
# CPU 환경 Multilingual 로딩 패치 (mtl_tts.py를 패치본으로 덮어씀)
# ⚠️ chatterbox/python 버전이 바뀌면 이 경로가 안 맞을 수 있음 → 그때 patches/mtl_tts.py 갱신
- ./chatterbox-tts-server/patches/mtl_tts.py:/usr/local/lib/python3.10/site-packages/chatterbox/mtl_tts.py
5) 재빌드로 검증
docker compose up -d --build chatterbox
docker compose logs -f chatterbox
이번엔 CUDA 에러 없이 로딩이 끝까지 가고, 로그에 다음 줄이 나오면 성공이다. 이미지를 새로 빌드해도 마운트가 패치본을 덮어쓰므로, 앞으로 재빌드해도 안 깨진다.
engine: Successfully loaded ChatterboxMultilingualTTS on cpu
server: TTS Model loaded successfully via engine.
왜 Dockerfile COPY가 아니라 볼륨 마운트인가? Dockerfile에 COPY로 박는 게 라이브러리 버전 변화에 더 강하다. 다만 관리 지점을 docker-compose.yml 하나로 모으고 싶다면 볼륨 마운트도 된다. 단점은 마운트 경로가
python3.10·chatterbox 버전에 묶여 있어서, 버전이 올라가면 경로가 안 맞아 깨질 수 있다는 것. 그땐patches/mtl_tts.py만 새 버전 기준으로 다시 만들면 된다.
'IT > Tech' 카테고리의 다른 글
| 티스토리 개인 도메인 등록 방법 (0) | 2026.05.30 |
|---|---|
| 🚀 네이버, 구글의 시대는 끝났다? AI 검색 '퍼플렉시티'가 검색엔진을 대체하는 이유 (0) | 2026.05.14 |
| 🤖 "결제 전 필독" 2026년 AI 6대 천왕 완벽 비교 (ChatGPT, Gemini, Claude, Perplexity, Grok, Manus) (0) | 2026.05.09 |
| 아직도 PPT 직접 만드니? Manus AI와 Gamma로 업무 끝내고 칼퇴하는 법 (할인 꿀팁까지 정리!) (1) | 2026.05.09 |
| [2026 최신] 구독료 다이어트 끝판왕! 겜스고(GamsGo)에서 가장 인기 있는 필수 구독 제품 TOP 10 총정리 💸 (0) | 2026.05.03 |