Getting started¶
This assumes jailbee is installed and jailbee setup has been run — see
Installation, whose last step is that command (shell
completions, the egress-refresh timer, and — if you opt in — agent skills for
the agents on your host).
Concepts¶
A quick mental model before the commands:
- One container per branch.
jailbee new <name>spins up a full-stack system container dedicated to one branch — its own services, Docker daemon, ports, and database — so several branches run in parallel without colliding. Think of<name>as the container's name: it's whatjailbee shell,jailbee ide,jailbee destroy, and the rest take. Since Incus names can't contain/, a name likefeat/my-featureis slugified to the container namefeat-my-feature. (Under the hood that same value is also the git branch checked out inside the container's clone, but day-to-day you can treat it as just the container name. Override the derived name with--name.) - Golden image, cloned copy-on-write.
jailbee base buildprovisions one image once (~10–15 min); everyjailbee newclones it copy-on-write, so new containers are cheap and fast. - Clone mode vs mount mode. By default the container gets its own
git cloneof the repo (clone mode), isolated from the host tree. With--mountit shares the host working tree directly instead. - Base branch + the git bridge. A clone-mode container remembers the
base branch it forked from and acts as a git remote: commits move
between host and container over a local bridge (
jailbee git checkout/pull/push) rather than through GitHub. - Network modes. Each container runs
strict(kernel egress allowlist) orloose(open egress, auto-reverts) — switch withjailbee net. - One shared state layer per repo. Package-manager caches, the JetBrains
config,
~/.sshand Claude's login live in a shared dir outside the containers. Most of it is bind-mounted into every container at once, so branches running in parallel share one warm pnpm cache and one set of tool settings — configure something in one container and the others have it. A few caches that a tool locks — Gradle, Maven, and the Chrome profile — instead give each container its own private copy, seeded from the warmest existing one, so concurrent builds don't contend on one lock file. Either way the state survivesjailbee destroy/jailbee newcycles and is a layer of its own, so nothing a container does leaks into your host's own dotfiles. Seeshared_cachesandpooled_caches.
Configure¶
jailbee is run from the root of the repo it manages. The repo must contain a
.jailbee/config.yaml file:
cd ~/path/to/your/repo
jailbee config init # creates a fully-populated template
jailbee doctor # sanity-check
All schema keys are optional — the defaults are stack-neutral and work for
any repo; add the language toolchains your project needs with
golden.stacks. See config.md for the full field
reference.
Multi-repo: Each jailbee-owned Incus resource (container, profile, ACL)
is prefixed with <container_prefix>-. The prefix defaults to your repo
directory name (repo_root.name) and must match [a-z0-9][a-z0-9-]*. If
your directory name contains underscores, dots, or capital letters, set
container_prefix: explicitly in .jailbee/config.yaml. Two repos on the
same host with distinct prefixes coexist without colliding. Use
jailbee ls --all to list containers from every jailbee-managed repo.
Per-developer settings: .jailbee/config.yaml is checked into the repo
and shared with your team, so anything personal lives in
~/.config/jailbee/global.yaml instead:
jailbee config init --global # writes a fully-commented ~/.config/jailbee/global.yaml
That file carries your host mounts (~/.gitconfig, ~/.gnupg), your IDE
preference, the Docker registry mirror port (default 3128, rpardini's
default), and the Claude Code opt-in — see
Claude Code in the container below. A third,
optional file holds what is personal and specific to one repo, such as its
GitHub token: ~/.config/jailbee/repos/<container_prefix>.yaml, edited with
jailbee config edit --local (see
Host-local overrides). The files are
deep-merged at load time, global then repo then host-local: a later layer wins
on scalar collisions, lists are appended, and blocks that hold host secrets
(github, credentials, litellm) are rejected in the committed repo file so
they can't leak via git.
Initialize and build¶
jailbee config validate # verify your config is sane
jailbee init # creates Incus profiles, ACLs, jailbee-loose bridge, shared dirs
jailbee registry up # Docker users only — see below
jailbee base build # builds the golden image (~10-15 min, one time)
jailbee registry up is only for repos whose image contains Docker
(golden.stacks.docker). Run it before your first strict-mode jailbee new:
the strict egress allowlist has no registry hosts, so the mirror is the
container's only route to Docker Hub — and a strict-mode jailbee new refuses
to create the container until the mirror is up (in loose mode it warns and
proceeds, since there the mirror is only a pull cache). Repos without Docker
skip this step — docker_registry_mirror.enabled defaults to auto and never
asks for the mirror container to exist.
A typical day¶
A clone-mode branch from creation to teardown. The host stays the hub: you bring the container's commits back to the host and push from there, so the container never needs network access for day-to-day work.
# 1. Spin up a container for a new branch (off the default branch).
jailbee new feat/my-feature # → container "feat-my-feature" (the / is slugified to -)
jailbee ls # watch it come up
# 2. Work inside it — a shell, or launch the IDE onto your desktop.
jailbee shell feat-my-feature # lands in the in-container clone
# … edit, run services, commit inside the container …
jailbee ide feat-my-feature # optional: JetBrains IDE on the host display
# 3. Bring the container's commits back to the host (local bridge, no network).
jailbee git checkout feat-my-feature # fast-forwards / creates the host branch
# 4. Push and open a PR from the host, as usual.
git push -u origin feat/my-feature
gh pr create # or your normal PR flow
# 5. Iterate: more commits in the container → checkout again → push.
jailbee git checkout feat-my-feature && git push
# 6. Snapshot before something risky; roll back if needed.
jailbee snapshot create feat-my-feature before-migration
jailbee snapshot restore feat-my-feature before-migration
# 7. Tear down when the branch is merged.
jailbee destroy feat-my-feature --force
Prefer to open the PR from the container (e.g. for an AI agent running
inside it)? jailbee pr feat-my-feature does that — but the container needs
network, so switch it out of strict first with
jailbee net loose feat-my-feature. See
Git bridge and branch workflows for pushing host→container,
stacked PRs, and reviewing existing PRs.
Claude Code in the container¶
Running a coding agent unattended is what the per-branch container and the
strict egress allowlist are for. The practical payoff is that you can
stop answering permission prompts: run Claude with
--dangerously-skip-permissions and let the container be the boundary
instead of the agent's own judgement. You size the blast radius once, in
the container's config — read-only mounts for what the build needs,
sensitive optional_mounts left detached, strict egress — rather than
adjudicating it one prompt at a time. JailBee has first-class support for
Claude Code — one of six shipped agent
presets, and the only one exercised in production. The same agents:
mechanism wires in any other terminal coding agent the same way; see
Generic agent support for the full list and how to add your
own. Turn Claude Code on in ~/.config/jailbee/global.yaml:
agents:
claude:
enabled: true
plugins_enabled: true
# autostart: true # launch claude on every container start
# command: claude --dangerously-skip-permissions # what the autostart window runs
The template written by jailbee config init --global already contains
this agents.claude block with enabled: true, so if you took that path
you have it already. Run jailbee apply after editing, then jailbee new
a container.
A top-level claude: block is still accepted and means the same thing, but
defining both spellings at once is a ConfigError — so if you have an older
claude: block, rename it rather than adding agents.claude alongside it.
What turning it on gets you:
- Claude Code installed in every container, from a version store shared across the repo's containers — no per-container download.
- One login, all containers.
<shared_dir>/claudeis mounted as~/.claude, so settings, MCP servers, agents and credentials are shared and survivejailbee destroy/jailbee newcycles. Claude Code's global config (.claude.json) lives inside that mount: the golden image exportsCLAUDE_CONFIG_DIR=$HOME/.claude, and Claude Code reads(CLAUDE_CONFIG_DIR || $HOME)/.claude.json. Your host~/.claudeis never read — Claude runs its onboarding once, inside the first container. - Strict mode still works. The Anthropic API and CLI-update hosts are
added to the egress allowlist automatically;
plugins_enabledadds the GitHub + npm hosts the plugin marketplace and skills reach. You don't list them by hand. - The agents know JailBee. JailBee's own
jailbee-usage,jailbee-repo-setupandjailbee-pr-reviewskills are copied into the shared skills directory of every enabled agent that has one (claude, codex, gemini, opencode), so the in-container agents can drivejailbeecommands for you. - AI-written PRs.
jailbee pr <name>asks the container's own agent — the one the repo autostarts, or whicheverpr.agentnames — for the title, body, and branch name (--no-aiopts out per call). Claude runs on Sonnet by default (pr.model), and the agent follows your project's own PR-writing rules if you state them inpr.prompt.
With autostart: true, every container comes up with Claude already
running in a tmux window; jailbee tmux <name> drops you straight into it.
That plus jailbee net strict <name> is the intended shape for unattended
runs. Before leaning on it, read
Running an agent without prompts
— it spells out exactly what an agent in that mode can and cannot reach,
including the parts of the shared state layer it can. See
config.md for every claude.* key and
pr for how jailbee pr picks the agent that writes PRs.
Next steps¶
- Commands — full command + flag reference
- Git bridge and branch workflows — moving commits between host and containers, stacked PRs, mount vs clone
- Setting up JailBee in your own project — adapting
jailbeeto your own repo and stack - Configuration reference — every
.jailbee/config.yamlkey