Skip to content

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 -u and id -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=1 is 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 failed Bash calls, $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's docs/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.