byre is open source on GitHub -- pjlsergeant/byre

Configuring the box

Save my LLM credentials so I don’t need to re-auth for each box?

tldr: say y when the first-run picker offers shared auth for your agent – or enable the relevant x-shared-auth skill(s) in byre config.

By default agents log in once per project, inside the box. The shared-auth skills (claude-shared-auth, codex-shared-auth, gemini-shared-auth, opencode-shared-auth) move that to once per machine. For claude, codex, and opencode every project’s first run asks: “Use machine-wide credentials to log in to <agent>?” – yes enables the skill for that project (its byre.config), and only for it. Saying yes to “Save these as your default?” remembers your answer like the template/agent favourites: the next box’s question just defaults to it, one Enter to accept. (Enabling the skill in byre config --global is the machine-wide route – then the question stops.) The login lives in a shared volume that reset/forget deliberately never touch. See the security model for the implications of this. (Gemini’s skill exists but is hand-enabled, not offered, until its two-box verification gate passes. Grok’s shared auth works differently – its token rotation can’t be file-shared, so a broker mediates instead, and until its field gate passes the skill is hand-enabled rather than offered – ADR 0036.)

Enable a skill in this box?

tldr: the Skills section of byre config.

Bundled skills toggle on the spot; installed and local ones appear once they exist on the machine. Everything a skill grants shows up in byre status the moment it’s enabled – what a skill is.

Add an MCP server to my agent’s session?

tldr: byre mcp add <name> <url> – or byre mcp add <name> -- <command...> for a local server; --global for every project.

--bearer TOKEN_NAME wires static-token auth (the header carries ${TOKEN_NAME}, expanded from the box env at launch – names travel, values don’t). byre mcp list shows the effective set with every entry attributed to the layer or skill that declared it; byre mcp remove is closure-smart, so removing a skill-declared server writes a !name entry instead of failing. Declarations bake into the image and inject into the agent session; a remote server’s host is attributed egress in byre status. The full story: MCP & Claude Skills.

Add a package to this box – and promote it to my template?

tldr: add it under Packages in byre config; when it belongs everywhere, move it into your template (fork the bundled one first: byre template fork byre/node my-node).

The first time you want a postgres client, it’s one entry under Packages in that project’s byre config. When it belongs everywhere you write node, fork the bundled template into an editable local one, add the package there, and set template = "my-node" – every project on that template gets it on its next develop. For literally-everywhere, byre config --global puts it in your personal baseline.

Set my defaults so every new box starts right?

tldr: byre config --global – your personal baseline, the bottom layer of every project’s cascade.

Anything a config can carry can live there: packages, mounts, skills, env, egress. The first-run picker’s favourites (template, agent, shared auth) are remembered separately, as preferences – your most-used answers become the pre-selected defaults.

Switch agents for one project?

tldr: the Agent section of byre config, then relaunch.

The agent key decides which enabled agent skill’s command launches in the foreground. Each agent keeps its login in its own state volume, so switching to codex for a week and back to claude costs no re-auth.

Run a different agent just this once?

tldr: byre develop -a codex – one run, nothing written.

The flag overrides the agent key for that session exactly as if the config said so: the agent’s skill is enabled for the run (its CLI, state volume, and egress ride the normal path), and the next plain byre develop is back on your config. Shared-auth companions never ride an override – they enter via the written config only. byre status shows the running box’s agent and names the override. Works for a fresh worktree too: byre worktree fix-thing -a codex forwards it to the new session – of an already-configured project; a repo that has never been developed refuses the flag rather than silently configuring itself from it.

Expose a port to see the box’s dev server?

tldr: the Ports section of byre config.

Published ports bind 127.0.0.1 by default – your browser reaches the box, your LAN doesn’t; opening a wider interface is an explicit, louder choice. Every published port shows in byre status.

Pass env vars into the box?

tldr: the Env vars section of byre config[env] for plain config values, never secrets, and [env_from_host] to pass host values at runtime.

[env] literals are baked into the image: docker history shows them and they outlive byre reset, so they’re for configuration, not credentials. [env_from_host] is the runtime channel and a legible grant: KEY = "env:HOST_VAR" passes a host env var at launch, "git:user.email" reads git config, "tz:" passes your timezone – values resolve at launch and never land in a layer. Git identity, TERM, and TZ already pass through by default.

A row can also carry the value itself, encrypted: that is a project credential, written by byre credentials set KEY or by the same Env screen, opened at launch with a passphrase. Use it when the value should live with the project rather than in your host environment.

Both live on one screen, because they answer one question – where does this variable’s value come from. The Source picker on a row switches between them, moving the entry rather than leaving a twin behind. One namespace is refused in both: BYRE_, which parameterizes byre’s own launch machinery; to set one deliberately use run_args = ["-e", "BYRE_X=..."].

Use my API key instead of an agent login?

tldr: pass it at runtime – [env_from_host] with OPENAI_API_KEY = "env:OPENAI_API_KEY" – never [env], which bakes it into the image.

The agents’ own login flows are still the better default: the credential lands in the agent’s state volume, per project, and the shared-auth skills make one login serve every box. But where an API key is the workflow, env_from_host keeps it out of the image, resolves it fresh at every launch, and shows as a named grant in byre status.

If the key should belong to the project rather than to your host environment, byre credentials set OPENAI_API_KEY stores it encrypted in the config itself and asks for the passphrase at launch – same table, same screen, no host env var to keep in sync.

Use Podman instead of Docker?

tldr: nothing – engine = "auto" (the default) picks docker if present, else podman.

Force it with engine = "podman" per project or globally. Rootful and rootless both work; rootless Podman 4.3+ runs under --userns=keep-id so files still land correctly owned. Everything else – volumes, images, status – reads identically across engines.

Mount other folders from the host?

tldr: the Mounts section of byre config.

Each mount is a host path, an in-box path, and a read-only/read-write choice (read-only is the default); every mount shows up in byre status under “Host mounts”.

Bring my dotfiles and shell setup into every box?

tldr: mount them read-only under Mounts in byre config --global – the box’s target mirrors your home path, so they land where the agent looks.

A home-relative host path (~/.config/starship.toml) suggests the matching /home/dev/... target automatically. Symlinks into a dotfiles repo work – byre reads through them.

Mounting is the whole answer here: [files] cannot do this. Its sources are project-relative and planFiles refuses an absolute path, a .. escape, and a symlink out of the tree, so a config can never name a file in your home directory. To bake a dotfile in rather than mount it, the file has to live in the project.

Give my agent standing instructions in every box?

tldr: byre context add house-rules opens your $EDITOR – --global for every box on the machine, plain for just this project; also the Instructions section of byre config.

Write the prose, save, quit – the git-commit shape. The scope is the layer you write: --global reaches every box on the machine, a named layer (byre config --layer <name>, Instructions) reaches its stack, the project config just that project. A later layer’s same-name entry replaces an inherited one, and the Instructions screen’s “Remove in this project” (or byre context remove <name>) switches one off; byre context list shows what’s in effect and whether it’s delivered.

How it reaches the agent. At build time byre composes one text – byre’s own box facts, each enabled skill’s guidance, then your instructions – and bakes it into the image at /etc/byre/agent-context.md. At launch, the agent’s own command injects that text into the session through the agent’s native mechanism:

AgentMechanism
Claude--append-system-prompt-file (appended to the system prompt)
Codexdeveloper_instructions config (a developer-role message)
Grok--append-system-prompt (session rules)
Geminia byre-owned context directory in its memory discovery
OpenCodean instructions entry in its injected config

Every mechanism appends – the agent’s built-in behavior and anything the project or you set up natively stay intact. byre never writes into the agent’s own files: your ~/.claude/CLAUDE.md (and every agent’s equivalent) is yours, inside the box as outside, and the repo’s own CLAUDE.md/AGENTS.md load exactly as the agent normally loads them, on top. An agent without an injection mechanism simply doesn’t receive the text – byre status and byre context list say so plainly rather than pretending.

Long documents can stay your own file: byre context add conventions --file ~/notes/agent-conventions.md reads it at each rebuild (machine-local – inline text is what travels in a preset). Because the config lives host-side, the boxed agent can’t rewrite its own standing orders – unlike a repo’s in-tree CLAUDE.md, which is the project’s (agent-writable) voice. (The one exception is deliberate: byre develop --self-edit hands the box its own project config, project-layer declarations included.)

A skill can still carry opinions via its own [context] table – that’s the right home when the prose should travel with a tool (it’s how byre’s own dev box enforces its diary and review habits). The stored form ([[context]] blocks) is in the configuration reference.

Stop re-downloading dependencies on every rebuild?

tldr: a [[volumes]] entry with role = "cache" on the dependency directory.

Templates ship the obvious ones (node_modules for node); add your own for anything regenerable that’s slow to fetch. Cache volumes survive rebuilds and relaunches, and losing one only costs a re-download. Volumes & state has the model.

Run project setup automatically?

tldr: build-time setup goes in dockerfile_post; per-launch setup is a small skill’s hook script.

dockerfile_post lines run at image build and cache like any Dockerfile step – the right home for installs and fetches that shouldn’t repeat every session. For something that must run at every launch (or once per fresh box), a skill’s [build] files can place a script into /etc/byre/firstrun.d/ (first run) or /etc/byre/env.d/ (sourced at every launch, just before the agent starts). There’s deliberately no config-level launch hook – launch behavior is a skill’s job, so it’s attributable.

Cap the box’s CPU or RAM?

tldr: run_args = ["--cpus=2", "--memory=4g"].

run_args is raw docker run passthrough, appended after byre’s own flags so yours win. byre never parses inside it; byre status degrades its posture claims honestly when it’s present. (Identity-changing flags – --user, --userns – are the one documented footgun: they break the baked-UID ownership model.)

Restrict network access?

tldr: enable the firewall skill in byre config, then pick what to open under Egress.

By default byre doesn’t restrict network access. The firewall skill flips that to deny-by-default: your box starts but runs nothing while a privileged one-shot helper joins its network namespace, installs the allowlist rules, and verifies them. Only then does the agent launch behind the wall – and if any of that fails, the box dies closed rather than running open.

Under “Egress” you choose what to open. The ports your selected agent needs open automatically, and your other skills may suggest more (eg GitHub) – those you open by hand, then relaunch.

One honest limit worth knowing: hostname grants are pinned to the IPs they resolved to at launch, so on DNS that rotates (CDNs, some cloud resolvers) a granted host can start failing – closed, never open – until a relaunch re-resolves it. Details in the security model.

Just want to block telemetry, not the internet? The firewall-open skill keeps the network open and drops only the hosts you block: egress = ["!statsig.anthropic.com"]. The same !host entries subtract from the full firewall’s allowlist too, skill-declared endpoints included.

Run other Docker containers from inside the byre environment?

tldr: enable the docker-host skill in byre config.

The skill installs the Docker CLI (plus compose and buildx) in the box and mounts the host daemon’s socket. It’s worth being clear-eyed about what you’re granting: anything that can run Docker on the host also has effective root on the host, and byre status disclaims that hole for as long as the skill is enabled. docs/DOCKER-HOST.md covers what the grant really means and when to prefer something narrower. Nested Podman (a daemon inside the box, granting nothing on the host) is unsupported.

Get the coding agent to edit its own byre config?

tldr: byre develop --self-edit – the box gets its own config mounted, and changes are shown on exit.

byre develop --self-edit will mount the box’s configuration directory on /home/dev/.byre-self and will also ship contextual documentation to your box telling your agent how to make edits. There are (of course!) some security implications to this, so it’s probably best not to always run in this mode. Changes to the configuration will be shown on exit.