Run the whole platform in your own VPC.
One compose command brings up the complete conversation-QA workspace on loopback, and the default stack opens no outbound connection.
- 0runtime deps
- 0egress · default path
- 1compose command
- 5workspace views
Small on purpose. Nothing to phone home.
Three parts, and only the first is required. The core is standard-library Python, so the default image adds no supply-chain surface and makes no external call at run time.
- hotato
- The pip-installable package. The core carries zero runtime dependencies.
- hotato serve
- The read-only, token-authenticated team workspace: five views over your registry and evidence store.
- Ollama
- An optional local model judge for the rubric lane, opt-in behind a compose profile. No hosted API on the default path.
Build, run, open. On loopback.
One compose command builds the image from source and starts the workspace, published to host loopback only. To reach it from your laptop, use an SSH tunnel or a reverse proxy you control.
- Build the image from source.
Multi-stage and non-root; the default build installs the stdlib-only core.
- Bring the workspace up.
Published to
http://127.0.0.1:8321, on127.0.0.1only. - Read the bearer token.
Generated on first start and stored owner-only; open the workspace with it.
- Ingest your own calls.
Your CLI runs in the same container against the same
/data. Everything the workspace shows comes from calls you ingest.
# build the image from local source docker compose build # bring the workspace up on loopback docker compose up -d # -> http://127.0.0.1:8321 # print the token the server generated docker compose exec hotato-workspace \ cat /data/serve/default/token # optional: seed a clearly-labelled example dataset docker compose run --rm hotato-init
$ docker compose up -d$ docker compose --profile judge up -d$ docker compose exec ollama ollama pull qwen2.5vl:3b- No published port
- The Ollama service is reachable only on the private compose network, wired to the workspace.
- One documented download
- The model pull is the single fetch that touches the network; after it, inference runs offline.
- Advisory lane
- The model verdict carries full provenance and never gates the deterministic checks unless you opt in.
A model judge that stays on the private network.
The rubric lane can score a transcript against criteria with a local model. Enable it with the judge profile; no hosted API is ever on the default path.
Because the endpoint hostname is not loopback, the endpoint gate asks you to acknowledge it before the workspace will use it, even though that traffic never leaves the host. The gate keys on the hostname alone.
Carry the image over. Run with no network.
The default workspace needs no network at run time, so it runs on an air-gapped host once the image is present. Bring the image over from a connected machine.
For the optional judge, pre-seed the model into a named volume on a connected machine and move that volume to the air-gapped host. With the image loaded and the model volume in place, the whole stack runs with no network access.
# on a connected machine docker compose build docker save hotato-selfhost:local \ | gzip > hotato-selfhost.tar.gz # move the tarball to the air-gapped host, then: gunzip -c hotato-selfhost.tar.gz | docker load docker compose up -d
The judge model is pre-seeded the same way: pull it into a named volume on a connected machine, back the volume up, and restore it on the target.
# everything lives in the /data volume: # registry, evidence store, token, audit log docker run --rm \ -v hotato_hotato-data:/data \ -v "$PWD":/backup alpine \ tar czf /backup/hotato-data-backup.tgz -C /data .
The evidence store is content-addressed, so a restored artifact is the same bytes that produced the original verdict; a digest that does not match is caught on read.
One volume to back up. One platform, either way.
Everything the workspace needs is in the /data volume. Tar that one volume; restore it into a fresh volume. A rebuild upgrades the image and leaves your registry and evidence untouched.
The self-hosted registry and evidence store use the same schemas the managed cloud uses. Your conversation artifacts, contracts, and dashboards move between self-hosted and cloud without changing a line.
Unplug the network. The workspace still answers.
A script in the repository runs the same image on a Docker network where egress is physically removed and shows the workspace still serves a view.
$ ./deploy/verify-zero-egress.sh- Only loopback
- Confirms
127.0.0.1:8321is the one published binding. - Network removed
- Runs the image on an internal Docker network and shows a view still returns a success.
- Nothing external
- Lists established connections inside the container and confirms none leave the host.