Files
voicebox/Dockerfile
T
harryandcapy-ai-staging[bot] ab9a19790c fix(docker): fix startup crash and permission errors in the container
Four related fixes uncovered while getting Docker running on a Linux
host without AVX-512 and with an NVIDIA GPU:

- pedalboard>=0.9.21 ships a Linux wheel with AVX-512 instructions
  baked into its native extension, which SIGILLs (exit 132) on import
  on any CPU without AVX-512 support. Pin below the regression until
  upstream fixes it (spotify/pedalboard#454).
- The rocminfo probe in app.py ran unconditionally on every build
  variant, always failing and logging on non-ROCm systems. Gate it on
  /dev/kfd actually being present.
- sox (required by qwen-tts's X-vector extractor) and a C compiler
  (required by Triton to JIT-compile its CUDA driver shim on first
  use) were missing from the runtime image, causing hard failures
  once those code paths were actually exercised.
- Named volumes and bind mounts (HF cache, app data, generated audio)
  are created root-owned by Docker on first use, but the app runs as
  the unprivileged voicebox user. chown the mount points in the
  entrypoint before dropping privileges so this self-heals on every
  start regardless of host UID.
2026-10-04 00:12:29 +00:00

131 lines
4.8 KiB
Docker

# ============================================================
# Voicebox — Local TTS Server with Web UI
# 3-stage build: Frontend → Python deps → Runtime
#
# Build variants:
# CPU (default): docker compose up --build
# ROCm (AMD GPU): docker compose -f docker-compose.yml -f docker-compose.rocm.yml up --build
# ============================================================
# Top-level ARG so it is visible to all stages.
ARG PYTORCH_VARIANT=cpu
# === Stage 1: Build frontend ===
FROM oven/bun:1 AS frontend
WORKDIR /build
# Copy workspace config and frontend source
COPY package.json bun.lock CHANGELOG.md ./
COPY app/ ./app/
COPY web/ ./web/
# Normalize line endings first (a Windows CRLF checkout would otherwise
# defeat the `-z 's/,\n ]/…/'` match below, since it's LF-anchored), then
# strip workspaces not needed for web build, and fix trailing comma
RUN sed -i 's/\r$//' package.json && \
sed -i '/"tauri"/d; /"landing"/d' package.json && \
sed -i -z 's/,\n ]/\n ]/' package.json
RUN bun install --no-save
# Build frontend (skip tsc — upstream has pre-existing type errors)
RUN cd web && bunx --bun vite build
# === Stage 2: Build Python dependencies ===
FROM python:3.11-slim AS backend-builder
# Re-declare ARG inside the stage (Docker scoping requirement).
ARG PYTORCH_VARIANT=cpu
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir --upgrade pip
COPY backend/requirements.txt .
# ROCm wheel index. Default 6.3 (RDNA1/2/3); set ROCM_VERSION=7.2 for RDNA4.
ARG ROCM_VERSION=6.3
# For ROCm, make the PyTorch ROCm index primary so every install below resolves
# torch to ROCm wheels instead of the default CUDA build.
RUN if [ "$PYTORCH_VARIANT" = "rocm" ]; then \
pip install --no-cache-dir --prefix=/install \
--index-url "https://download.pytorch.org/whl/rocm${ROCM_VERSION}" \
torch torchaudio && \
printf '[global]\nindex-url = https://download.pytorch.org/whl/rocm%s\nextra-index-url = https://pypi.org/simple\n' "$ROCM_VERSION" > /etc/pip.conf; \
fi
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
RUN pip install --no-cache-dir --prefix=/install --no-deps chatterbox-tts
RUN pip install --no-cache-dir --prefix=/install --no-deps hume-tada
RUN pip install --no-cache-dir --prefix=/install \
git+https://github.com/QwenLM/Qwen3-TTS.git
# === Stage 3: Runtime ===
FROM python:3.11-slim
# Create non-root user; the entrypoint joins GPU device groups at runtime.
RUN groupadd -r voicebox && \
useradd -r -g voicebox -m -s /bin/bash voicebox
# Create HuggingFace cache directory for the named volume
RUN mkdir -p /home/voicebox/.cache/huggingface \
&& chown -R voicebox:voicebox /home/voicebox/.cache/huggingface
# Create voice generations directory for the named volume
RUN mkdir -p /app/data/generations \
&& chown -R voicebox:voicebox /app/data/generations
WORKDIR /app
# Install only runtime system dependencies (gosu drops root in the entrypoint).
# sox is required by qwen-tts's speech_vq X-vector extractor, which shells
# out to it via the `sox` Python bindings for reference-audio normalization.
# gcc/g++ are required by Triton (used by some torch ops, e.g.
# bmm_outer_product) to JIT-compile its CUDA driver shim on first use --
# without them, PyTorch's CUDA-capable build crashes with "Failed to find
# C compiler" even when a GPU is actually present.
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \
sox \
gcc \
g++ \
curl \
gosu \
&& rm -rf /var/lib/apt/lists/*
# Copy installed Python packages from builder stage
COPY --from=backend-builder /install /usr/local
# Copy backend application code
COPY --chown=voicebox:voicebox backend/ /app/backend/
# Copy built frontend from frontend stage
COPY --from=frontend --chown=voicebox:voicebox /build/web/dist /app/frontend/
# Create data directories owned by non-root user
RUN mkdir -p /app/data/generations /app/data/profiles /app/data/cache \
&& chown -R voicebox:voicebox /app/data
# Expose the API port
EXPOSE 17493
# Health check — auto-restart if the server hangs
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=60s \
CMD curl -f http://localhost:17493/health || exit 1
# Entrypoint joins GPU groups then drops to the voicebox user.
# Normalize CRLF (a Windows checkout otherwise leaves the shebang as
# `#!/bin/sh\r`, which Linux can't resolve — reported as a misleading
# "no such file or directory" even though the file exists).
COPY --chmod=755 scripts/rocm-entrypoint.sh /usr/local/bin/entrypoint.sh
RUN sed -i 's/\r$//' /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "17493"]