Guides
Documentation
Agent in isolated container

Docker & OrbStack

Run the agent in an isolated container with Docker or OrbStack — full Rails stack, a ready-made Compose file.

Docker and OrbStack run the agent in an isolated container with a complete Rails stack and every native library preinstalled. The agent's work — clones, bundle install, migrations, asset builds — stays inside its volumes, and the repo ships a Compose file so a single command brings it up.

Which runtime?

Apple Container

Tightest isolation — each agent runs in its own lightweight VM. The lean, native choice on a recent Mac.

Apple silicon · macOS 15+

Docker & OrbStack

You're here

Runs anywhere Docker does, Intel or ARM, with a ready-made Compose file. OrbStack keeps it light on a Mac.

Docker or OrbStack · Intel or Apple silicon

Not the right fit? Set up Apple Container instead.

Install the runtime

Install OrbStack — recommended on a Mac — or Docker Desktop, then confirm the daemon is running.

brew install orbstack          # recommended — or install Docker Desktop
docker --version && docker info

The agent runs entirely inside the container, so the only other tool you need on the Mac is Claude Code — used once to mint the agent's token. Skip this if you already have it.

npm install -g @anthropic-ai/claude-code

Get the image

Pull the published image and tag it local/fragua:latest so the guide and the Compose file line up. Pulling skips the 10–15 minute build and gives you the exact released image.

docker pull ghcr.io/maquina-app/fragua-docker:latest
docker tag ghcr.io/maquina-app/fragua-docker:latest local/fragua:latest
docker image ls | grep fragua
What the image bundles

The image is a complete Rails environment built on Ubuntu 24.04. You don't have to trust that blindly — here's what's inside, and a command to confirm it yourself.

Language runtimes
Ruby and Node, managed by mise. A project's own .mise.toml overrides the global versions automatically.
Ruby toolchain
Bundler and Rails preinstalled, with build flags wired to the system libraries so native gems like nokogiri, pg, mysql2, and hiredis compile without fuss.
Native libraries
The full bundle-install stack: build tools, SSL and crypto, SQLite, PostgreSQL and MySQL, image and media (ImageMagick, libvips, WebP, ffmpeg), PDF (wkhtmltopdf plus fonts), libxml, Protobuf and gRPC, geospatial (GEOS, PROJ), and Pango/Cairo rendering.
Command-line tools
GitHub CLI, git, Claude Code, and the Fragua CLI — everything the agent needs to clone, build, and push.

Confirm the stack from inside the container:

ruby --version && node --version && rails --version
sqlite3 --version && pg_config --version && mysql_config --version
ffmpeg -version | head -1 && wkhtmltopdf --version
gh --version && claude --version && fragua --version

Run these in the one-time setup shell below, or any container shell. Every line should print a version.

Want to change what's installed — add a library, pin a version, build multi-arch? The Dockerfile and build script live in the fragua-tools repo, with notes on customizing and publishing your own image.

Create volumes and sign in once

Everything the agent needs lives in four named volumes, so the running agent depends on nothing from your Mac. You set them up once; they survive every rebuild.

Create the four volumes

docker volume create fragua-config
docker volume create fragua-secrets
docker volume create fragua-workdir
docker volume create fragua-data
docker volume ls | grep fragua
fragua-config
/fragua-config

Fragua token and local status, your Git identity, the Claude token, and Claude Code's session and per-project state.

fragua-secrets
/fragua-secrets

The GitHub CLI token and the container's own SSH key, together. Read-only once the agent is running, so it can use but never rewrite them.

fragua-workdir
/fragua-workdir

The agent's working tree — clones, bundle install, databases, compiled assets.

fragua-data
/fragua-data

The Claude Code and fragua CLIs, plus the gems and Node packages the agent installs at runtime — kept so a rebuild doesn't refetch them.

Generate the Claude token (on your Mac)

Claude Code's interactive login needs a browser, which a headless container can't open. Mint a long-lived token on the Mac instead — you'll paste it into a volume next, and the container reads it from there on every run.

claude setup-token

Open the one-time setup shell

Open a one-time shell with the config and secrets volumes mounted writable. This is the only time the secrets volume is writable — at runtime it drops to read-only, so the agent can use the GitHub token and SSH key but never rewrite them.

docker run --rm -it \\
  -v fragua-config:/fragua-config:rw \\
  -v fragua-secrets:/fragua-secrets:rw \\
  local/fragua:latest bash

Inside the container, create the secret directories, then sign in to GitHub. The device-code flow works headless — it prints a code and a URL. The --insecure-storage flag writes the token onto the secrets volume so it persists; without it gh may stash it in an in-VM keyring that's gone on the next run.

mkdir -p /fragua-secrets/gh /fragua-secrets/ssh && chmod 700 /fragua-secrets/ssh
gh auth login --insecure-storage

Give the container its own SSH key rather than copying your personal one. A dedicated key is isolated from your Mac and you can revoke it on its own. Set a Git identity, generate the key, and register it on GitHub:

git config --global user.name  "Your Name"
git config --global user.email "[email protected]"

chmod 700 /root/.ssh
ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N "" -C "fragua-docker"
gh auth refresh -h github.com -s admin:public_key
gh ssh-key add /root/.ssh/id_ed25519.pub --title "fragua-docker"
ssh -o IdentitiesOnly=yes -i /root/.ssh/id_ed25519 -T [email protected]

Using https:// remotes instead of [email protected]? Skip the key entirely and run gh auth setup-git so gh acts as git's credential helper.

Advanced: you can reuse an existing host key by mounting it in and copying it once, but only if it has no passphrase — at your own risk.

Paste the token from claude setup-token into the config volume. The image's entrypoint reads this file on every run, so you never pass it again.

printf '%s' 'PASTE_THE_setup-token_VALUE' > /fragua-config/claude-oauth-token
chmod 600 /fragua-config/claude-oauth-token
claude -p "reply with the single word: ok"

Finally, connect the Fragua CLI with the AgentToken from your Settings page, check the setup, and leave the shell.

fragua login
fragua doctor
exit

Run the agent

A compose.yaml mounts only the four volumes, no host paths. Grab it below, bring it up, follow the logs, and check the agent's status.

fragua-config rw
/fragua-config
fragua-secrets ro
/fragua-secrets
fragua-workdir rw
/fragua-workdir
fragua-data rw
/fragua-data
compose.yaml

Save this as compose.yaml in the directory you run docker compose from. It mounts only the four volumes — no host paths, nothing wired into your Mac.

services:
  fragua-agent:
    image: local/fragua:latest
    volumes:
      - fragua-config:/fragua-config:rw     # fragua status/DB, git identity, claude token + state
      - fragua-secrets:/fragua-secrets:ro   # gh token + SSH key (read-only at runtime)
      - fragua-workdir:/fragua-workdir:rw   # agent working tree
      - fragua-data:/fragua-data:rw         # claude + fragua CLIs, runtime gems + node modules
    restart: unless-stopped

volumes:
  fragua-config:
    external: true
  fragua-secrets:
    external: true
  fragua-workdir:
    external: true
  fragua-data:
    external: true
docker compose up -d
docker compose logs --follow
docker compose exec fragua-agent fragua status

Prefer a one-off docker run? Here's the equivalent of the Compose file:

docker run -d --name fragua-agent --restart unless-stopped \\
  -v fragua-config:/fragua-config:rw \\
  -v fragua-secrets:/fragua-secrets:ro \\
  -v fragua-workdir:/fragua-workdir:rw \\
  -v fragua-data:/fragua-data:rw \\
  local/fragua:latest

Day to day

Rebuild or refresh the agent. Every volume survives docker compose down and image rebuilds, so there's no re-setup.

# rebuild — every volume survives, no re-setup
docker compose exec fragua-agent fragua prune --yes
docker compose down
docker build --no-cache -t local/fragua:latest .
docker compose up -d --force-recreate   # recreate so the new image's CLIs are adopted (plain up -d/start keeps the old container)

# update the CLIs (Claude Code + fragua) a running agent uses — no rebuild
docker compose exec fragua-agent fragua-refresh-cli   # both (or: claude / fragua)
# (refresh only the image's offline baseline instead: ./build.sh --refresh-cli)

# inspect the workdir without touching the running agent
docker run --rm -v fragua-workdir:/data:ro alpine ls -la /data

# drop the runtime gem/node cache + CLIs (re-bootstrapped on next start, keeps creds)
docker volume rm fragua-data && docker volume create fragua-data

Troubleshooting

claude /login fails with "Invalid OAuth Request / Unknown scope"
The browser redirect can't finish in a headless container. Run claude setup-token on your Mac and write the result to /fragua-config/claude-oauth-token.
git push fails with "Permission denied (publickey)"
The container's key isn't on GitHub. Re-run gh ssh-key add /root/.ssh/id_ed25519.pub in the setup shell, then test with ssh -T [email protected].
gh says "not logged in" once the agent is running
The fragua-secrets volume isn't mounted or is empty. Re-run gh auth login --insecure-storage in the setup shell, and confirm the volume is listed in your compose.yaml.
HTTPS git fails "could not read Username for https://github.com"
No git credential helper is wired to the gh token — the agent was set up SSH-only. Run gh auth setup-git once in the setup shell; it writes the helper into /fragua-config/gitconfig and persists.
claude refuses --dangerously-skip-permissions "cannot be used with root/sudo privileges"
The image runs as root by design and sets IS_SANDBOX=1 so Claude Code recognizes the isolated container. If you hit this, you're on an older image built before that env var — rebuild, or set IS_SANDBOX=1 when you invoke claude.
An x86 host can't run an arm64 image
Publish a multi-arch image with ./build.sh --platform linux/amd64,linux/arm64 so both Intel and ARM hosts can pull the same tag.