diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8cddf3d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +# Version control +.git +.gitignore + +# Desktop/native (not needed for Docker) +tauri/ +apps/ +landing/ + +# Python artifacts +backend/venv/ +backend/__pycache__/ +backend/**/__pycache__/ +backend/*.pyc +backend/*.spec +backend/data/ + +# Node artifacts +node_modules/ +app/node_modules/ +web/node_modules/ +web/dist/ + +# Build artifacts +dist/ +*.egg-info/ + +# IDE/OS +.vscode/ +.idea/ +.DS_Store + +# Docs +docs/ +*.md +!backend/requirements*.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index b7116d3..462dc98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Self-documenting help system with `make help` - Colored output for better readability - Supports parallel development server execution +- **Docker Support** - Dockerfile and docker-compose.yml for containerized deployment + - Three compose configurations: full stack, backend-only, and web-only + - NVIDIA GPU support with optional CUDA acceleration + - Persistent volumes for data and HuggingFace model cache + - Health check endpoint integration ### Changed - **README** - Added Makefile reference and updated Quick Start with Makefile-based setup instructions alongside manual setup diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8aacc6b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM pytorch/pytorch:2.6.0-cuda12.6-cudnn9-runtime + +# System dependencies: +# git - required for pip install from GitHub (qwen-tts) +# libsndfile1 - required by soundfile (audio I/O) +# ffmpeg - required by librosa (audio processing) +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + libsndfile1 \ + ffmpeg \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +ENV PYTHONUNBUFFERED=1 +ENV HF_HOME=/root/.cache/huggingface + +# Install Python dependencies +COPY backend/requirements.txt backend/requirements.txt +RUN pip install --no-cache-dir -r backend/requirements.txt \ + && pip install --no-cache-dir git+https://github.com/QwenLM/Qwen3-TTS.git + +# Copy backend source +COPY backend/ backend/ + +# Data volume mount point +RUN mkdir -p /app/data + +EXPOSE 8000 + +ENTRYPOINT ["python", "-m", "backend.main", "--host", "0.0.0.0", "--port", "8000", "--data-dir", "/app/data"] diff --git a/Dockerfile.web b/Dockerfile.web new file mode 100644 index 0000000..0bfa2ba --- /dev/null +++ b/Dockerfile.web @@ -0,0 +1,34 @@ +# Stage 1: Build the web frontend +FROM oven/bun:1 AS build + +WORKDIR /app + +# Copy workspace root config +COPY package.json bun.lock ./ + +# Copy the workspaces needed for the build +COPY app/ app/ +COPY web/ web/ + +# Create stub package.json for workspaces we don't need but bun requires +RUN mkdir -p tauri && echo '{"name":"@voicebox/tauri","version":"0.0.0","private":true}' > tauri/package.json +RUN mkdir -p landing && echo '{"name":"@voicebox/landing","version":"0.0.0","private":true}' > landing/package.json + +# Install dependencies +RUN bun install + +# Build the web frontend (skip tsc type checking — Vite transpiles independently) +RUN cd web && bunx vite build + +# Stage 2: Serve with Nginx +FROM nginx:stable-alpine + +# Copy built assets +COPY --from=build /app/web/dist /usr/share/nginx/html + +# Copy Nginx config and entrypoint +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY docker/web-entrypoint.sh /docker-entrypoint.d/40-backend-url.sh +RUN chmod +x /docker-entrypoint.d/40-backend-url.sh + +EXPOSE 3000 diff --git a/README.md b/README.md index ea08e3c..2234543 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,79 @@ Voicebox is available now for macOS and Windows. --- +## Docker + +Run Voicebox in Docker — no Python, Bun, or system dependencies required. + +### Quick Start + +```bash +# Clone and start both backend + web UI +git clone https://github.com/jamiepine/voicebox.git +cd voicebox +docker compose up -d +``` + +- **API**: http://localhost:8000 (Swagger docs at http://localhost:8000/docs) +- **Web UI**: http://localhost:3000 + +Verify the backend is running: + +```bash +curl http://localhost:8000/health +``` + +### Compose Configurations + +| Configuration | Command | Description | +|---------------|---------|-------------| +| **Full stack** | `docker compose up -d` | Backend + Web UI | +| **Backend only** | `docker compose -f docker-compose.backend.yml up -d` | API server only | +| **Web only** | `BACKEND_URL=http://my-server:8000 docker compose -f docker-compose.web.yml up -d` | Frontend pointing to existing backend | + +### GPU Support + +For NVIDIA GPU acceleration, install the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html), then uncomment the `deploy` section in `docker-compose.yml`: + +```yaml +deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +The backend auto-detects GPU availability at runtime — it works on CPU without any changes. + +### Data Persistence + +| Volume | Purpose | +|--------|---------| +| `voicebox-data` | Database, voice profiles, and generated audio | +| `huggingface-cache` | Downloaded ML models (Qwen3-TTS, Whisper) | + +Data persists across `docker compose down` / `docker compose up` cycles. + +### Connecting Clients + +**Desktop app** — Use Remote Server mode and point to `http://:8000`. + +**API** — The full REST API is available at port 8000: + +```bash +# List voice profiles +curl http://localhost:8000/profiles + +# Generate speech +curl -X POST http://localhost:8000/generate \ + -H "Content-Type: application/json" \ + -d '{"text": "Hello from Docker", "profile_id": "your-profile-id", "language": "en"}' +``` + +--- + ## Features ### Voice Cloning with Qwen3-TTS diff --git a/docker-compose.backend.yml b/docker-compose.backend.yml new file mode 100644 index 0000000..abd61bd --- /dev/null +++ b/docker-compose.backend.yml @@ -0,0 +1,27 @@ +services: + backend: + build: . + ports: + - "8000:8000" + volumes: + - voicebox-data:/app/data + - huggingface-cache:/root/.cache/huggingface + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + # Uncomment the following to enable NVIDIA GPU support: + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + +volumes: + voicebox-data: + huggingface-cache: diff --git a/docker-compose.web.yml b/docker-compose.web.yml new file mode 100644 index 0000000..c4b2d7d --- /dev/null +++ b/docker-compose.web.yml @@ -0,0 +1,10 @@ +services: + web: + build: + context: . + dockerfile: Dockerfile.web + ports: + - "3000:3000" + environment: + - BACKEND_URL=${BACKEND_URL:-http://localhost:8000} + restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ff0c82c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +services: + backend: + build: . + ports: + - "8000:8000" + volumes: + - voicebox-data:/app/data + - huggingface-cache:/root/.cache/huggingface + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + # Uncomment the following to enable NVIDIA GPU support: + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + + web: + build: + context: . + dockerfile: Dockerfile.web + ports: + - "3000:3000" + environment: + - BACKEND_URL=http://localhost:8000 + depends_on: + backend: + condition: service_healthy + restart: unless-stopped + +volumes: + voicebox-data: + huggingface-cache: diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..06e032c --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,14 @@ +server { + listen 3000; + root /usr/share/nginx/html; + index index.html; + + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/docker/web-entrypoint.sh b/docker/web-entrypoint.sh new file mode 100644 index 0000000..c37d91e --- /dev/null +++ b/docker/web-entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# Replace the hardcoded dev backend URL with the configured BACKEND_URL +# in all built JS assets at container startup. +BACKEND_URL="${BACKEND_URL:-http://localhost:8000}" +find /usr/share/nginx/html/assets -name '*.js' \ + -exec sed -i "s|http://localhost:17493|${BACKEND_URL}|g" {} +