Running AI Workbench with Docker
The published container image bundles the TypeScript runtime and the React web UI into a single process on port 8080. This guide covers the end-user "try it" path; for source-from-Node development see the top-level README.md.
Quickstart
curl -O https://raw.githubusercontent.com/datastax/ai-workbench/main/docker-compose.yml
docker compose upOpen http://localhost:8080. The first compose up pulls ghcr.io/datastax/ai-workbench:latest (multi-arch — amd64 and arm64). Subsequent boots are near-instant.
Without compose
If you'd rather not use compose:
docker run --rm \
-p 8080:8080 \
-v workbench-data:/var/lib/workbench \
-e WORKBENCH_CONFIG=/app/examples/workbench.docker.yaml \
ghcr.io/datastax/ai-workbench:latestSame behavior: file-backed persistence on the named volume, healthcheck baked into the image.
Persistence
The compose stack mounts a named volume workbench-data at /var/lib/workbench. The bundled workbench.docker.yaml pins the control plane to the file driver rooted there, so workspaces / agents / KBs / jobs survive docker compose down.
Where the volume lives
docker volume inspect ai-workbench_workbench-dataOn Docker Desktop this is inside the VM disk; on Linux Engine it's under /var/lib/docker/volumes/.
Backup and restore
# Backup
docker run --rm \
-v ai-workbench_workbench-data:/data \
-v "$(pwd):/backup" \
alpine tar czf /backup/workbench-backup.tgz -C /data .
# Restore (into a fresh stack)
docker compose down -v
docker compose up -d --no-start
docker run --rm \
-v ai-workbench_workbench-data:/data \
-v "$(pwd):/backup" \
alpine tar xzf /backup/workbench-backup.tgz -C /data
docker compose up -dReset to a clean slate
docker compose down -v
docker compose up -d-v removes the named volume; the stack restarts with no workspaces.
Configuration
The runtime reads workbench.yaml for backend selection and per-feature toggles (auth, chat, MCP). Environment variables override matching keys at boot — see docs/configuration.md for the full schema.
Precedence (highest wins): shell env > container env > .env > workbench.yaml defaults.
Astra DB credentials
Workspace-scoped Astra credentials resolve from process env at use time, so adding them to .env is enough — no yaml edit required:
# .env (next to docker-compose.yml)
ASTRA_DB_API_ENDPOINT=https://<db-id>-<region>.apps.astra.datastax.com
ASTRA_DB_APPLICATION_TOKEN=AstraCS:...docker compose up -d --force-recreate reloads env.
When you create a workspace in the UI / API with credentialsRef: { token: "env:ASTRA_DB_APPLICATION_TOKEN" }, the runtime resolves the ref from these vars.
Note: the bundled
workbench.docker.yamlpins the control plane (where workspace / agent / KB metadata is stored) to the local file volume. To put the control plane itself in Astra, use the override file pattern below to pointWORKBENCH_CONFIGat a custom yaml withcontrolPlane: { driver: astra, ... }.
OpenRouter chat
Generate a key at https://openrouter.ai/keys, add it to .env:
OPENROUTER_API_KEY=sk-or-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # secret-scan: allowOne key reaches OpenRouter's 300+ models. For fully offline use, point the chat: block at the ollama provider instead — Ollama runs locally and needs no key.
Then either uncomment the chat: block in the bundled workbench.docker.yaml (via the override pattern) or bind-mount a custom yaml with chat: enabled. Without chat: and no agent-level LLM binding, POST .../messages returns 503 chat_disabled.
Override file pattern
docker-compose.override.yml (gitignored) is merged on top of docker-compose.yml automatically. Use it for the common deviations:
cp docker-compose.override.yml.example docker-compose.override.yml
# edit, then:
docker compose up -d --force-recreateThe example file shows three pre-canned snippets:
- Build from source — replace
image:with abuild:block pointing atruntimes/typescript/Dockerfile. - Bind-mount a custom yaml — swap
WORKBENCH_CONFIGto a path under/etc/workbench/and bind-mount your file there read-only. - Bump log verbosity — set
LOG_LEVEL=debug.
Upgrading
docker compose pull
docker compose up -d --remove-orphanspull fetches the newest :latest (or whatever tag you've pinned via AI_WORKBENCH_TAG). Recreating preserves the named volume.
To pin a version:
AI_WORKBENCH_TAG=0.3.0 docker compose up -dHealthcheck
The image ships with a HEALTHCHECK that hits /healthz every 30s. Status surfaces in docker ps:
docker compose ps
# NAME STATUS PORTS
# ai-workbench Up 2 minutes (healthy) 0.0.0.0:8080->8080/tcp/readyz adds config-load gating; use it from external probes that need to know the runtime finished startup.
Troubleshooting
Port already in use. Override the host-side port:
AI_WORKBENCH_PORT=9090 docker compose up -dVolume permission denied on Linux. Named volumes inherit container user perms on first use; if you reused a volume that was created with a different uid, fix it with:
docker run --rm -v ai-workbench_workbench-data:/data alpine \
chown -R 1000:1000 /data(The image runs as node = uid 1000.)
View logs.
docker compose logs -f workbenchPull denied / unauthorized. New GHCR packages default to private, so right after the first release the image isn't pullable without auth. A DataStax org member with admin:packages rights flips it with the bundled helper:
# One-time per gh login:
gh auth refresh -s admin:packages
# After the first release lands:
npm run release:make-public
# → "Flipped to public. `docker pull` now works without auth."The script (scripts/ghcr-make-public.mjs) is idempotent — re-running on an already-public package is a no-op — and exits with a hint if the package doesn't exist yet (no release has shipped). Until the flip happens, authenticate manually:
echo $GITHUB_TOKEN | docker login ghcr.io -u <user> --password-stdinManifest unknown / no matching arch. The :latest tag is multi-arch (amd64 + arm64) as of the first release that ships this guide. Older tags may be amd64-only — pin a newer tag or rebuild from source via the override file.