Home / Self-host

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
your cloud · your VPC
YOUR VPC loopback only · 127.0.0.1:8321 hotato serve read-only · 5 views bearer-token auth SQLite registry evidence store · sha256 /data volume local judge · Ollama optional · private network no published port your browser SSH tunnel / reverse proxy you control internet unreached no outbound
The default stack, drawn from the compose file: the workspace, its registry, and its evidence store, all inside your boundary. The judge is opt-in and never leaves the private network.
The stack

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.

The five workspace views →

Bring it up

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.

  1. Build the image from source.

    Multi-stage and non-root; the default build installs the stdlib-only core.

  2. Bring the workspace up.

    Published to http://127.0.0.1:8321, on 127.0.0.1 only.

  3. Read the bearer token.

    Generated on first start and stored owner-only; open the workspace with it.

  4. Ingest your own calls.

    Your CLI runs in the same container against the same /data. Everything the workspace shows comes from calls you ingest.

from the repository root
# 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
the one command
$ docker compose up -d
the judge profilelocal
enable the local model judge
$ docker compose --profile judge up -d
pull a model once
$ 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.
The local judge

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.

How the model judge stays advisory →

Air-gapped deployment

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.

save · move · load
# 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.

back up the single volume
# 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.

Backup & migration

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.

Zero-migration promise

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.

Prove it yourself

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.

The full posture, command by command →

the proof, in the repo200 · offline
run the zero-egress proof
$ ./deploy/verify-zero-egress.sh
Only loopback
Confirms 127.0.0.1:8321 is 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.