Docker
The sidecar is published as a multi-arch image (linux/amd64, linux/arm64) at ghcr.io/miraistudio/spendctl. It holds the same files as the spendctl npm package plus its production dependencies, runs as an unprivileged user and keeps all state under /data. Tags: 0.2.0, 0.2 (moves with patch releases) and latest; pin at least the minor version in production. Same licence as the npm package (/app/LICENSE in the image).
Start the sidecar
Section titled “Start the sidecar”Mount the directory that holds spendctl.yaml at /data:
mkdir spend-demo && cd spend-demodocker run --rm -v "$PWD:/data" ghcr.io/miraistudio/spendctl:0.2 initdocker run -d --name spendctl -v "$PWD:/data" -p 127.0.0.1:8402:8402 ghcr.io/miraistudio/spendctl:0.2The image’s default command is start --host 0.0.0.0, so the sidecar accepts connections from outside the container. Publish the port only where your agents are: 127.0.0.1:8402:8402 keeps it on the host. Agents then send requests to http://localhost:8402 exactly as in Integrations; the dashboard is at http://localhost:8402/_spendctl/dashboard.
Human commands
Section titled “Human commands”pending, approve, budget, ledger and export talk to the running sidecar and read the admin token from .spendctl/. Run them in the sidecar’s network namespace with the same mount:
alias spendctl='docker run --rm --network container:spendctl -v "$PWD:/data" ghcr.io/miraistudio/spendctl:0.2'spendctl pendingspendctl approve --latestspendctl budgetspendctl export --format csv --out receipts.csv # written to $PWDIf Node.js is installed on the host, npx spendctl pending from the same directory works too. The image also puts spendctl on the container’s PATH, so docker exec spendctl spendctl pending runs the command inside the running sidecar container without starting a second one.
The quickstart, Docker only
Section titled “The quickstart, Docker only”The mock facilitator and its two paid APIs can share the sidecar’s network namespace, so localhost means the same thing everywhere:
docker run -d --name spendctl-mock --network container:spendctl -v "$PWD:/data" ghcr.io/miraistudio/spendctl:0.2 mockspendctl fetch "http://localhost:4021/weather?city=Tokyo" --agent researcher# ✔ 200 paid $0.01Continue with the Quickstart from “Repeat it”, using the alias above instead of npx spendctl.
Docker Compose
Section titled “Docker Compose”services: spendctl: image: ghcr.io/miraistudio/spendctl:0.2 volumes: ["./:/data"] ports: ["127.0.0.1:8402:8402"] restart: unless-stopped agent: build: . environment: SPENDCTL_URL: http://spendctl:8402 # read by @spendctl/client and the spendctl CLI depends_on: [spendctl]Once other containers can reach the sidecar, identify agents by token instead of by header: set agentAuth: token in spendctl.yaml and issue tokens with spendctl token <agent> (Policy).
Linux hosts
Section titled “Linux hosts”The container runs as uid 100. On Linux the bind-mounted directory belongs to your user, so add --user "$(id -u):$(id -g)" to each docker run (or user: in Compose) so the sidecar can write .spendctl/. Docker Desktop on macOS and Windows maps the ownership for you.
Updating
Section titled “Updating”Pull the new tag and recreate the container. The ledger and the tokens live in .spendctl/ on the host, so nothing is lost:
docker pull ghcr.io/miraistudio/spendctl:0.2docker rm -f spendctl && docker run -d --name spendctl -v "$PWD:/data" -p 127.0.0.1:8402:8402 ghcr.io/miraistudio/spendctl:0.2