A safe-by-default .devcontainer setup for AI-assisted development. Copy this
directory into a project, open the project in VS Code, and build the
devcontainer from the Dev Containers UI.
For implementation details, see DESIGN.md.
Prerequisites:
- Docker Desktop, OrbStack, or another Docker engine with Compose support
- VS Code
- VS Code Dev Containers extension
Install:
- Copy this directory as
.devcontainerinto the target project. - Open the project folder in VS Code.
- Run
Dev Containers: Reopen in Container. - VS Code runs
.devcontainer/host/jail-initautomatically and builds the services. - After the container opens,
jail checkruns.
Host-owned jail state lives outside the project at:
$HOME/.devcontainer-jail
Set DEVCONTAINER_JAIL_HOME before launching VS Code if you want that state in
a different location.
Inside the worker:
jail checkExpected result: every check prints OK.
To test egress approval:
curl -I https://example.comBefore approval, this creates a pending request and holds the client connection.
The worker exposes one helper command:
jail check
jail bootstrap
jail agent-login codex
jail agent-login claude
jail tmux snapshot
jail tmux restoreRun the host operator from the project root:
.devcontainer/host/jail-operatorUse it to approve, deny, revoke, change TTLs, inspect active SSH leases, manage the blacklist, and run the host SSH broker. It live-refreshes pending requests.
Pending requests support approve, deny, and forever-deny. Active approvals can
be revoked or moved between finite TTLs and forever. Active SSH leases appear
in the leases view with their TTLs and can be revoked there. Recent brokered
SSH sessions appear in the sessions view.
Run agent login inside the worker:
jail agent-login codex
jail agent-login claudeThis bypasses Headroom for auth by running the equivalent of
env -u OPENAI_BASE_URL codex loginorenv -u ANTHROPIC_BASE_URL claude.
Agent state is kept in jail-owned Docker volumes, not mounted from your host agent config directories.
Proxy-aware network requests are default-deny. Unknown HTTP/HTTPS targets create or update one pending request per target. If approved before the client times out, the original request continues.
Host raw-TCP services configured in customizations.jail.bridgePorts are
reachable at proxy:<port>. For example, Postgres on the host at 5432 should
use proxy:5432 from inside the worker.
For package-manager work, run the package manager normally. Unknown network targets will appear in the operator for approval.
Node/JavaScript tooling is enabled by default. pnpm, uv, and mise are the
preferred package and toolchain managers.
Install Python with uv or mise, and Go/Rust with mise, on demand inside
the worker. The alias m is available for mise. User and per-repo toolchain
state lives under persisted /home/node/.local paths.
For shell customizations, edit ~/.zshrc.local; it is persisted under
/home/node/.local/config/shell/zshrc and sourced on startup.
Rebuilds recreate containers and kill running tmux processes. Save and restore the layout with:
jail tmux snapshot
jail tmux restoreThe snapshot is stored at .devcontainer/.jail/tmux-layout.json. It includes
all tmux sessions, windows, panes, working directories, and best-effort pane
commands, except managed sessions listed in JAIL_MANAGED_TMUX_SESSIONS, which
are started automatically.
Edit a pane's command field before restore when needed.
SSH keys stay on the host. The worker has no private keys and no raw
SSH_AUTH_SOCK.
Run the operator, then use SSH normally inside the worker:
ssh root@your-tailnet-node
ssh git@github.comThe first matching invocation creates a pending SSH request. If approved, the
host broker runs real host SSH and relays stdin/stdout/stderr back to the jailed
process. Git uses the same path through GIT_SSH=/usr/local/bin/ssh.
Forwarding and proxy-style SSH features are intentionally denied by the broker.
The optional host Chrome bridge is configured in
customizations.jail.hostChrome in .devcontainer/devcontainer.json.
By default it starts on macOS during devcontainer initialization when open,
lsof, and Google Chrome are available. It uses the dedicated profile:
$HOME/.chrome-remote-test
Agents can connect to the host browser's CDP endpoint at:
http://host.docker.internal:9222
The worker image includes:
Headroom starts automatically in a detached worker tmux session named by
JAIL_HEADROOM_TMUX_SESSION.
Ponytail is installed, but plugins are not silently trusted. Install plugins from the agent UI or CLI. If plugin installation needs GitHub, approve that egress request in the operator.