The container edition
Holdout Lab’s container edition runs the whole application — API, worker, and the built web UI — under Docker Compose with zero host toolchain. It is the supported way to install and run the product; most people get it as the downloadable bundle — see Install with Docker for that path. This page covers what makes the container edition different from a source install, and how to run the same stack from a source checkout.
How the container edition differs
Section titled “How the container edition differs”The api and worker run with ATS_DEPLOYMENT_MODE=container, which adapts the app to
compose-managed infrastructure:
- Keys persist on a shared volume. In-app Settings writes to
/app/config/.env(theconfignamed volume, viaATS_ENV_FILE) instead of a file inside one container — both the api and the worker read the same file, and it survives image updates. - The worker restarts itself cleanly. The in-app Restart worker button sets a Redis
flag; the worker notices within seconds, exits gracefully, and compose’s
restart: unless-stoppedrelaunches it — the fresh process picks up newly saved keys. (In a terminal,docker compose restart workerdoes the same.) - Gateways are compose-managed. The in-app IB gateway buttons show the matching
docker composecommands instead of starting containers themselves (the app never touches the Docker socket). The runner reaches the gateway by service name on the compose network:ib-gateway-paper:4004— 4004 is the container’s socat forward of the paper API; the familiar 4002 exists only as a host-published port in the host-run mode. - Host-only surfaces disappear. Settings → System swaps launchd agents and
scripts/*backups for volume-based guidance (pg_dumpvia compose).
Everything else — research, backtests, the gauntlet, Studio, charts, crypto paper trading — behaves identically to the host-run mode. The crypto sandbox is fully self-contained in-container: live Kraken public market data, no keys, no broker account.
Run it from a source checkout
Section titled “Run it from a source checkout”The same edition builds from the repo’s compose file (profile app):
scripts/setup.sh # once: scaffolds backend/.env with a session secretdocker compose -f infra/docker-compose.yml --profile app up --buildOpen http://localhost:8080. Add --profile ib to run the IB paper gateway alongside it.
The stack is:
| Service | Image | Role |
|---|---|---|
web |
infra/Dockerfile.web |
React bundle behind nginx, SPA fallback for the router’s URLs, proxies /api (incl. the WebSocket feed) to the api |
api |
infra/Dockerfile.api |
FastAPI; runs alembic upgrade head on start so a fresh stack self-initializes; ships the docs for the in-app help assistant |
worker |
infra/Dockerfile.worker |
arq worker: ingestion, backtests, the session relay |
postgres / redis |
stock images | same services the host-run mode uses |
Data and artifacts live in named volumes (pgdata, appdata, artifacts, config), so
ingested bars, run results, and saved keys survive restarts and rebuilds.
The distributed bundle
Section titled “The distributed bundle”Releases publish multi-arch images to GHCR and a small compose bundle (pinned image tags +
.env template + setup.sh) to holdoutlab.com/download.
The bundle’s compose file is the image-based equivalent of the app profile — same volumes,
same service names, same edition behavior. Install/update/backup steps live in
Install with Docker.
Related
Section titled “Related”- Install with Docker — the customer install path.
- Deployment (host-run / developer mode)
- Database & migrations