Skip to content

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.

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 (the config named volume, via ATS_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-stopped relaunches it — the fresh process picks up newly saved keys. (In a terminal, docker compose restart worker does the same.)
  • Gateways are compose-managed. The in-app IB gateway buttons show the matching docker compose commands 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_dump via 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.

The same edition builds from the repo’s compose file (profile app):

Terminal window
scripts/setup.sh # once: scaffolds backend/.env with a session secret
docker compose -f infra/docker-compose.yml --profile app up --build

Open 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.

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.