Docker¶
Operational guide only. For authoritative storage and metadata structure, see STORAGE.md. For behavior and invariants, see CONTRACT documents.
Docker Compose is the recommended way to run TranscriptX in containers (no local Python required). The Streamlit web interface runs inside the container with a mounted data directory.
This guide describes container behavior and operational layouts only. Canonical storage, output, and run-truth rules live in:
docs/runtime/STORAGE.mddocs/contracts/output-contract-v1.mddocs/run_outcome_contract.md
TranscriptX is analysis-only; it does not run WhisperX or any transcription engine inside Docker. Bring your own transcript JSON (see transcription.md for how to generate compatible transcripts).
Non-root /data write access¶
The default compose runs the transcriptx-web service as your host user (user: "${UID:-1000}:${GID:-1000}") so that files written under the mounted ./data volume are owned by you.
Dev / quick start: If
/datais not writable (e.g. permission denied), make the host directory writable:chmod -R a+w data/(or createdataand then run compose).Production: Use the same
user: "${UID:-1000}:${GID:-1000}"so the container runs as a known UID/GID; ensure the host./datais owned by that user or is group-writable.
Quickstart¶
Copy .env.example to .env and set HOST_RECORDINGS_DIR to an absolute path outside the repository. Then:
docker compose up transcriptx-web
Open http://localhost:8501. Compose builds the image on first run. First-time path: README and First analysis.
Build¶
docker build -t transcriptx:latest .
Set TRANSCRIPTX_TORCH_VARIANT at build time to control which PyTorch wheels are installed:
Value |
When to use |
|---|---|
|
Linux with NVIDIA GPU ( |
|
Mac / Apple Silicon, or any CPU-only host. Skips 500MB+ NVIDIA deps; uses PyTorch CPU wheels. |
With Compose (reads from .env or docker-compose.override.yml):
# Mac local dev (docker-compose.override.yml sets cpu by default)
docker compose build
# Explicit CPU build
TRANSCRIPTX_TORCH_VARIANT=cpu docker compose build
# GPU-oriented build (Linux)
TRANSCRIPTX_TORCH_VARIANT=default docker compose build
Plain docker build:
docker build --build-arg TRANSCRIPTX_TORCH_VARIANT=cpu -t transcriptx:latest .
Multi-arch (e.g. for publishing):
docker buildx build --platform linux/amd64,linux/arm64 -t transcriptx:latest .
The image includes the spaCy language models (en_core_web_sm, en_core_web_md, and en_core_web_lg), so NLP modules (topic modeling, NER, etc.) work out of the box—no need to run python -m spacy download inside the container.
Apple Silicon (M1/M2/M3): Prefer a CPU torch build — docker-compose.override.yml sets TRANSCRIPTX_TORCH_VARIANT=cpu for local dev. Docker on Mac cannot use GPU acceleration anyway. If arm64 builds still fail, you can build the amd64 image under emulation:
docker buildx build --platform linux/amd64 --load -t transcriptx:amd64 .
export HOST_RECORDINGS_DIR=/path/to/your/recordings # outside the repo; same as in .env for compose
docker run --rm -v "$(pwd)/data:/data" -v "$HOST_RECORDINGS_DIR:/mnt/recordings" \
-e TRANSCRIPTX_RECORDINGS_DIR=/mnt/recordings --platform linux/amd64 -p 8501:8501 transcriptx:amd64
Primary commands: Web interface¶
Start the web interface (port 8501):
docker compose up transcriptx-web
Then open http://localhost:8501 in your browser.
Host bind address (security)¶
Compose publishes the web port as:
ports:
- "${TRANSCRIPTX_BIND_HOST:-127.0.0.1}:8501:8501"
Default:
127.0.0.1(loopback only). Prefer this for local single-user use.LAN opt-in:
TRANSCRIPTX_BIND_HOST=0.0.0.0 docker compose up(recreate the single service; do not start a second stack).Inside the container the app still uses
--host 0.0.0.0so the published host port can reach it.
Threat: binding to 0.0.0.0 grants unauthenticated LAN access to transcripts, generated artefacts, configuration-visible operations, and destructive cleanup actions in the UI. See SECURITY.md.
Canonical CI/release Compose proofs use only docker-compose.yml (or COMPOSE_FILE=docker-compose.yml) so a local docker-compose.override.yml cannot contaminate evidence.
You can also run docker compose up (without a service name) to start the web interface.
Scripting / automation (one-off Python API):
docker run --rm \
-v "$(pwd)/data:/data" \
-w /data \
transcriptx:latest \
python -c "
from transcriptx.app.models.requests import AnalysisRequest
from transcriptx.app.workflows.analysis import run_analysis
from pathlib import Path
result = run_analysis(AnalysisRequest(
transcript_path=Path('/data/transcripts/foo_transcriptx.json'),
modules=['stats'],
))
print('success:', result.success)
"
Operational modes¶
Mode |
Command |
|---|---|
Web interface |
|
Unfamiliar-user / curated samples |
|
Custom host/port |
|
Unfamiliar-user / disposable sample stack¶
docker-compose.unfamiliar-user.yml is a standalone Compose project (name: transcriptx-unfamiliar). It does not merge docker-compose.override.yml and does not touch the main ./data library.
Default host mounts (override with HOST_UNFAMILIAR_* env vars):
Host path |
Container |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
UI: http://127.0.0.1:8502 (TRANSCRIPTX_UNFAMILIAR_PORT to change). Use this for facilitator-supplied samples during unfamiliar-user validation.
After the first boot (or any wipe of ../transcriptx_test/data), ensure the epoch marker exists — Streamlit writes perf/ under /data, which makes an unmarked root fail the schema-epoch gate (missing_marker):
python -c "from pathlib import Path; from transcriptx.core.utils.schema_epoch import write_epoch; write_epoch(Path('../transcriptx_test/data'))"
Pre-copied managed transcripts under the transcripts mount are not auto-registered. Home counts the slug index (HOST_UNFAMILIAR_OUTPUT_DIR/.transcriptx_index.json). After the stack is up:
docker compose -f docker-compose.unfamiliar-user.yml exec -T transcriptx-web python - <<'PY'
from transcriptx.core.utils.file_discovery import discover_managed_transcript_paths
from transcriptx.io.admit_and_register import _try_register
for p in discover_managed_transcript_paths():
print(_try_register(p), p.name)
PY
Then reload the UI (no recreate required if the volume is already mounted).
Volume layout¶
Compose mounts app working data at /data and maps host folders to container paths via HOST_* variables in .env (see .env.example):
volumes:
- ./data:/data
- ${HOST_CONFIG_DIR:-./data/.transcriptx}:/data/.transcriptx
- transcriptx_cache:/home/transcriptx/.cache
- ${HOST_TRANSCRIPTS_DIR:-./data/transcripts}:/mnt/transcripts:ro
- ${HOST_TRANSCRIPT_INBOX_DIR:-./data/transcript-inbox}:/mnt/transcript-inbox:ro
- ${HOST_OUTPUT_DIR:-./data/outputs}:/mnt/outputs
- ${HOST_RECORDINGS_DIR}:/mnt/recordings
- ${HOST_RECORDINGS_DIR}/imports:/mnt/recordings/imports
- ${HOST_WAV_BACKUP_DIR:-./data/backups/wav}:/mnt/wav
HOST_RECORDINGS_DIR is required in .env and must point at a host folder outside the repository (your source-audio library). Create imports/ under that folder for uploads if needed.
Prefer HOST_CONFIG_DIR outside the clone as well (same idea as transcripts/outputs): it holds config.json (including saved custom questions), interface menus, and analysis profiles. Default remains ./data/.transcriptx.
Host variable |
Container path |
App env ( |
Notes |
|---|---|---|---|
(default) |
|
|
App cache, groups, speaker_profiles, HF caches. Numba/librosa cache is |
|
|
|
Project settings / metadata ( |
|
|
|
Read-only in base compose |
|
|
(scan path only) |
External inbox for Import all from folder; not under managed transcripts |
|
|
|
Analysis run outputs |
|
|
|
Source audio (read-only root) |
|
|
|
Writable uploads staging |
|
|
|
WAV archive |
Local dev override: docker-compose.override.yml (optional, often gitignored) repeats these mounts but drops :ro on transcripts so the web UI can write speaker-map sidecars beside JSON files. It also mounts ./tests/fixtures at /mnt/fixtures:ro for deep-test analysis probes. Analyse those paths in place (TRANSCRIPTX_ALLOW_UNMANAGED_TRANSCRIPTS=1); do not import them into /mnt/transcripts. For production-like read-only transcripts, use only docker-compose.yml or remove the override.
Canonical storage layout and invariants: docs/runtime/STORAGE.md.
Environment variables¶
Variable |
Default |
Description |
|---|---|---|
|
(required in |
Host path to source audio library (outside repo) |
|
|
Host path mounted at |
|
|
Host path mounted at |
|
|
Host path mounted at |
|
|
Host path mounted at |
|
|
Host path mounted at |
|
|
Max upload size in MB per file. Set in compose so the container allows 500 MB; without it Streamlit defaults to 200 MB. |
|
|
Base data directory inside container |
|
|
Project config + menus + profiles ( |
|
|
Source audio |
|
|
Writable upload staging |
|
|
Transcript JSON files |
|
|
Analysis outputs |
|
|
Longitudinal speaker profiles (PII) including enrolled voice under |
|
|
WAV archive |
|
|
Enable model/resource downloads ( |
|
|
Streamlit bind host |
|
|
Streamlit port |
Optional model overrides (TRANSCRIPTX_SPACY_MODEL, TRANSCRIPTX_SEMANTIC_MODEL, etc.) and LLM / Ollama settings (TRANSCRIPTX_LLM_ENABLED, TRANSCRIPTX_LLM_BASE_URL, TRANSCRIPTX_CORRECTIONS_LLM_ENABLED, …) are passed from the host .env into the container. See models.md, llm.md, and corrections-llm.md. On Mac/Windows Docker, point TRANSCRIPTX_LLM_BASE_URL at http://host.docker.internal:11434 so the container can reach Ollama on the host. Local docker-compose.override.yml defaults LLM + Corrections Studio discovery on when those vars are unset.
Health check¶
The compose file includes a health check that pings the Streamlit health endpoint:
docker compose ps # shows health status
Pitfalls¶
Port conflict: If 8501 is taken, override with
--port 8502or setTRANSCRIPTX_PORT.Permissions: Ensure the
./datadirectory is writable by the UID/GID used in compose.Model downloads: Runtime downloads are enabled by default. Set
TRANSCRIPTX_DISABLE_DOWNLOADS=1for offline/no-download runs and provide pre-populated caches as needed.Upload “AxiosError: Network Error”: If the file uploader shows this for large files, the server limit or a reverse proxy may be blocking the request. Compose sets
STREAMLIT_SERVER_MAX_UPLOAD_SIZE=500; if you use a proxy in front, increase its body size and timeouts (e.g. nginxclient_max_body_sizeandproxy_read_timeout).