Skip to content

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).

Mount the directory that holds spendctl.yaml at /data:

Terminal window
mkdir spend-demo && cd spend-demo
docker run --rm -v "$PWD:/data" ghcr.io/miraistudio/spendctl:0.2 init
docker run -d --name spendctl -v "$PWD:/data" -p 127.0.0.1:8402:8402 ghcr.io/miraistudio/spendctl:0.2

The 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.

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:

Terminal window
alias spendctl='docker run --rm --network container:spendctl -v "$PWD:/data" ghcr.io/miraistudio/spendctl:0.2'
spendctl pending
spendctl approve --latest
spendctl budget
spendctl export --format csv --out receipts.csv # written to $PWD

If 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 mock facilitator and its two paid APIs can share the sidecar’s network namespace, so localhost means the same thing everywhere:

Terminal window
docker run -d --name spendctl-mock --network container:spendctl -v "$PWD:/data" ghcr.io/miraistudio/spendctl:0.2 mock
spendctl fetch "http://localhost:4021/weather?city=Tokyo" --agent researcher
# ✔ 200 paid $0.01

Continue with the Quickstart from “Repeat it”, using the alias above instead of npx spendctl.

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).

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.

Pull the new tag and recreate the container. The ledger and the tokens live in .spendctl/ on the host, so nothing is lost:

Terminal window
docker pull ghcr.io/miraistudio/spendctl:0.2
docker rm -f spendctl && docker run -d --name spendctl -v "$PWD:/data" -p 127.0.0.1:8402:8402 ghcr.io/miraistudio/spendctl:0.2