Guides
Documentation
Agent in isolated container

Apple Container

Run the agent in a fully isolated macOS VM — full Rails stack, nothing exposed to your Mac.

Apple Container runs each agent in its own lightweight VM 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 nothing in your Mac's home folder is exposed to it.

Which runtime?

Apple Container

You're here

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

Apple silicon · macOS 15+

Docker & OrbStack

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 Docker & OrbStack instead.

Install the runtime

Install the Apple Container CLI from its releases page — a .pkg you double-click — then start the runtime. The first start installs a Linux VM kernel; answer Y when it asks.

open https://github.com/apple/container/releases/latest
container --version
container system start
container system status

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 rest of the guide's commands line up. Pulling skips the 10–15 minute build and gives you the exact released image.

container image pull ghcr.io/maquina-app/fragua-container:latest
container image tag ghcr.io/maquina-app/fragua-container:latest local/fragua:latest
container image list | 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

container volume create fragua-config
container volume create fragua-secrets
container volume create fragua-workdir
container volume create fragua-data
container volume list
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.

container 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-container"
gh auth refresh -h github.com -s admin:public_key
gh ssh-key add /root/.ssh/id_ed25519.pub --title "fragua-container"
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

The agent mounts only these named volumes — no host paths. The config and work volumes stay writable; the GitHub token and SSH key drop to read-only so the agent can use but never rewrite them.

fragua-config rw
/fragua-config
fragua-secrets ro
/fragua-secrets
fragua-workdir rw
/fragua-workdir
fragua-data rw
/fragua-data
container run \\
  --name fragua-agent \\
  --detach \\
  -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

Watch the agent, rebuild it, or wipe it. Every volume survives a rebuild, so there's no re-login and no lost work.

container logs --follow fragua-agent
container exec -it fragua-agent bash

# rebuild — every volume survives, no re-login
container exec fragua-agent fragua prune --yes
container stop fragua-agent && container rm fragua-agent
container build --no-cache -t local/fragua:latest .

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

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

Troubleshooting

container system start hangs on "Verifying machine API server"
The VM kernel isn't installed. Run container system kernel set --recommended, then container system start again.
claude /login fails with "Invalid OAuth Request / Unknown scope"
The browser redirect can't finish in a headless container. Don't use /login — 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].
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 the image
Images built on Apple silicon are arm64, and Apple Container is arm64-only. For Intel hosts, build and publish a matching image using the Docker guide's multi-arch build.