install
One container, on your machine
UsageFoundry runs as exactly one process, with its port published on 127.0.0.1 — compose publishes UF_BIND_ADDRESS:UF_PORT, which default to that address and port 3000. It is not designed to be scaled horizontally, and a second replica behind a load balancer is not a supported deployment.
quick start
Clone it and bring it up
git clone https://github.com/Xapicc/UsageFoundry.gitcd UsageFoundrycp .env.example .env# edit .env: UF_WORKSPACE (required — the code you want agents to work on)# UF_AUTH_TOKEN (required: openssl rand -hex 32) docker compose up --buildopen http://localhost:3000
Copied from the product README as it is written there. DATA_DIR is set for you by docker-compose.yml; it is checked before the server serves anything, and the container exits naming the path and the uid if it cannot be written.
- UF_WORKSPACE
- Required. The code you want agents to work on — this is the mount everything else is scoped to.
- UF_AUTH_TOKEN
- Required.
openssl rand -hex 32. Without it the server refuses to start, because every route is otherwise open to whoever reaches the port — including the one that starts billed agents with write access. - UF_ALLOW_NO_AUTH=1
- The deliberate override. The app starts, logs a block at boot, and banners every page saying it is unauthenticated.
- UF_UID / UF_GID
- On Linux, set these to
id -uandid -g. Linux preserves the host uid across a bind mount and a mismatch fails silently — git refuses every repository and the first write of a run fails.
first run
The dashboard works before the runs do
The ~/.claude mount carries your transcripts but not your credentials, so usage is there immediately and runs are not. Sign the container in once at Settings → Claude account → Sign in: it opens an Anthropic link, you approve, you paste the code back. The same row signs it out.
Sign in from the page rather than a shell. The login is dropped to the uid that work cycles run as; a docker compose exec defaults to root, and a .credentials.json at mode 0600 owned by the wrong uid fails every run with Not logged in — which looks exactly like not having signed in at all.
To reach it across your network: UF_BIND_ADDRESS=0.0.0.0, a token set, UF_ALLOW_NO_AUTH blank, and UF_COOKIE_SECURE=0 — a browser never returns a Secure cookie over plain HTTP.
ANTHROPIC_API_KEY in the environment outranks the subscription login: that key is what runs bill against, and the Settings row says so rather than reporting an account nothing uses.
trust boundary
What an unattended agent can reach
Self-hosted means the data stays here. It does not mean the parts are sealed off from each other, and the product’s own security document is the place this page takes its wording from rather than softening it.
- inside
- Your Claude account, every mounted workspace,
UF_GITHUB_TOKEN, and any plugin you switch on. A work cycle runs as the uid that owns the mounted~/.claude, because that is the only way it can authenticate at all. - outside
- The app's own secrets —
UF_AUTH_TOKEN,ANTHROPIC_ADMIN_KEY. The server runs as a different uid from the agents for exactly this. - beside
- A concurrent run's checkout, and the branch that checkout will land. Worktrees separate commits, not files.
- not yet
UF_SANDBOX=1is meant to confine a work cycle further. It is off by default, and the docs are blunt that no work cycle has yet run inside a sandbox that started: one install spent fifteen hours with it set and the sandbox silently not starting — ten runs, 214 failedBashcalls, $407 of spend, on 2026-08-18 and 2026-08-19. The figures come off that install's own run records, written up in the product'sdocs/verification.md. That write-up bounds the last of them: $407 is what the ten runs spent while degraded, not what the degradation cost.
Enabling a plugin means the container runs whatever that directory ships — hooks, agents, skills, commands, an MCP server — as the agents' uid, on every work cycle from the next one onward, runs already in flight included. The Plugins tab says which of those each one carries before you press it, and the switch saves on press rather than on Save.