# OmniVoice Studio — Install with Docker For headless servers, dedicated GPUs, or "I want one command" deployments. The docker image bundles the backend; the UI is served over HTTP and you open it in a normal browser. **Official images:** [`ghcr.io/debpalash/omnivoice-studio`](https://github.com/debpalash/OmniVoice-Studio/pkgs/container/omnivoice-studio) and [`palashdeb/omnivoice-studio` on Docker Hub](https://hub.docker.com/r/palashdeb/omnivoice-studio) — same images, same tags. > **Image ↔ version mapping** > > | Tag | What you get | > |-----|--------------| > | `:latest` | **Rolling preview** — latest commit on `main` (always one patch ahead of the last release). This is the preview channel; pin `:stable` for production. | > | `:stable` | Most recent versioned release (updated on every `v*` git tag) | > | `:0.3.6` | Exact release version | > | `:0.3` | Latest patch within the 0.3 minor | > | `:main` | Alias of the same rolling `main` build as `:latest` | > | `:sha-xxxxxxx` | Specific commit (produced by manual workflow dispatch) | > > Versioning rule: `main` always carries *last release + 1 patch*, so `:latest` > (preview) version-sorts above `:stable` — upgrades flow naturally. > > **Note on the update-channel toggle:** The update-channel UI (Settings → About → Update channel) is part of the Tauri desktop app's built-in auto-updater. It does **not** apply to the Docker image — the Docker image is the headless web-server build. To update your Docker deployment, pull the new image tag and recreate the container (`docker compose pull && docker compose up -d`). ## Pull and run (CPU) ```bash docker pull ghcr.io/debpalash/omnivoice-studio:latest docker run -d --name omnivoice \ -p 127.0.0.1:3900:3900 \ -v omnivoice-data:/app/omnivoice_data \ -v ~/.cache/huggingface:/root/.cache/huggingface \ ghcr.io/debpalash/omnivoice-studio:latest ``` > **Docker Hub mirror:** the same images are published to > `palashdeb/omnivoice-studio` on Docker Hub with identical tags — swap the > image for `palashdeb/omnivoice-studio:latest` if you prefer Docker Hub. > Tag semantics (`:latest` = rolling main preview, `:stable`/`:X.Y.Z` = > releases) are the same on both registries. Open [http://localhost:3900](http://localhost:3900). The first run downloads ~2.4 GB of model weights — follow `docker logs -f omnivoice` to watch. ## Pull and run (NVIDIA GPU) ```bash docker run -d --name omnivoice --gpus all \ -p 127.0.0.1:3900:3900 \ -v omnivoice-data:/app/omnivoice_data \ -v ~/.cache/huggingface:/root/.cache/huggingface \ ghcr.io/debpalash/omnivoice-studio:latest ``` GPU mode requires the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) on the host. ## Docker Compose (recommended) ```bash # CPU docker compose -f deploy/docker-compose.yml --profile cpu up -d # NVIDIA GPU docker compose -f deploy/docker-compose.yml --profile gpu up -d ``` The `docker-compose.yml` shipped in `deploy/` defaults to `127.0.0.1:3900` on the host. The backend inside the container binds to `0.0.0.0` so the host port mapping can forward — the host-side `127.0.0.1` binding is what enforces loopback-only. ## LAN access To expose OmniVoice on your LAN (e.g. you're running it on a homelab box and opening the UI from a laptop), change the host port mapping: ```yaml # deploy/docker-compose.yml services: omnivoice: ports: - "0.0.0.0:3900:3900" # ← was 127.0.0.1:3900:3900 ``` The OmniVoice frontend defaults to the **same origin** the page was served from, so opening the UI from `http://:3900` Just Works for both the page load *and* the API/media requests it makes afterwards. If you front the app with a **reverse proxy** and the API and UI land on different origins, pin the API base explicitly. Use **`OMNIVOICE_PUBLIC_API_BASE`** — a *runtime* env var the backend injects into the page, so it works with the prebuilt image via `docker run -e` (the older `VITE_OMNIVOICE_API` is inlined at *build* time and cannot be set on a prebuilt image): ```bash docker run -e OMNIVOICE_PUBLIC_API_BASE=https://api.your-host.example \ -p 0.0.0.0:3900:3900 \ ghcr.io/debpalash/omnivoice-studio:latest ``` > `OMNIVOICE_PUBLIC_API_BASE` must be a plain `http(s)://…` URL; anything else > is ignored and the app falls back to same-origin. If you build from source you > may instead bake `VITE_OMNIVOICE_API` at build time, but the runtime var above > is simpler and image-agnostic. > **Security:** OmniVoice ships no authentication. Anything on your LAN with > the URL can use the app. Put it behind a reverse proxy with `basic_auth` > (Caddy / nginx + htpasswd) or a private network overlay (Tailscale, ZeroTier) > before exposing publicly. ## Volume mounts Two paths are worth persisting across container restarts: | Mount | Purpose | Why | |-------|---------|-----| | `omnivoice_data:/app/omnivoice_data` | Project DB, user voices, settings | Survives upgrade; encrypted HF token lives here | | `~/.cache/huggingface:/root/.cache/huggingface` | HF model cache | Re-using your host's cache saves ~2.4 GB of re-downloads | ## Troubleshooting - **Container reports 0.2.7 but image is tagged 0.3.x:** This was a workflow bug (fixes #249, #251) — the `:latest` tag was not being updated on release tag pushes. Pull the image again after the fix is merged: `docker pull ghcr.io/debpalash/omnivoice-studio:latest`. The running version is now shown in **Settings → About → Version** (read live from the backend), so the web UI no longer displays a dash in Docker. - **Checking which version is running:** `docker exec omnivoice python -c "import importlib.metadata; print(importlib.metadata.version('omnivoice'))"`, or hit the `/health` endpoint — it returns `{"status": "ok", "device": ..., "version": "0.3.x"}`. - **"Loopback origin required" errors (and a blank version):** the desktop build restricts the `/system/*` and `/api/settings/*` routes to a loopback origin, but Docker's NAT makes every request look non-loopback, so the gate used to 403 the whole admin UI (issue #261). The image now ships with `OMNIVOICE_SERVER_MODE=1`, which relaxes that gate for the headless deployment — exposure is instead governed by your `-p` port mapping (keep the `127.0.0.1:` prefix to stay local) plus the optional share PIN. If you front the container with your own auth proxy on loopback, set `OMNIVOICE_SERVER_MODE=0` to re-enable the strict gate. - **Media-preview 404 in LAN mode:** see the [LAN access](#lan-access) section above — the `window.location.host` fix shipped in v0.3. - **GPU not detected:** verify `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu22.04 nvidia-smi` succeeds first. - More entries: [docs/install/troubleshooting.md](troubleshooting.md).