Sandboxed AI coding agents on Linux. CLI-first, per-sandbox isolation, BYO Docker.
aias runs one or more AI coding agents (Claude Code, Codex CLI, OpenCode)
inside a Docker container per project, with optional Docker-in-Docker,
your project source bind-mounted, and OAuth tokens mounted read-only.
- User guide — install, run, persistence, ports, GPU, SSH keys
- Workflow guide — day-to-day usage patterns
- Share modes — how much host Claude state flows into a sandbox
- Isolation and threat model — what the sandbox actually defends against
- Hosting — Kata Containers install runbook for
kata-clh - Testing — unit suite, integration bench, manual smokes
- Project — your codebase / repo (lives on the host).
- Sandbox — what aias creates around a project: a container, a
per-sandbox image, named volumes, and a
.aias/directory with configuration. One sandbox per project.
v0.6.0 (current): a single aias:base image is the sole base; DinD is an
opt-in Docker-CE topping in the per-sandbox .aias/Dockerfile (toggle with
aias dind on|off). Agents (claude / codex / opencode) install into a shared
volume rather than the base image. Three sandbox runtimes per sandbox (docker /
runsc / kata-clh). Multi-agent CLI dispatch. Opt-in credential sharing via a
[secrets] block, plus host-state share modes. Prebuilt-base distribution via
[image] base_registry + aias image push|pull. Opt-in host-port publishing via
[ports] publish (loopback by default; cross-sandbox host-port collision check at
start). Per-sandbox memory sizing via [runtime] memory (raises a kata-clh
microVM's RAM above its fixed ~2 GB default; a plain cgroup cap under
docker/runsc). Feature-complete for daily use.
- Linux (Arch, Ubuntu, Debian, Fedora — anywhere Docker runs)
- Docker 24.0+ with user in
dockergroup - Per-agent subscriptions: a Claude Pro/Max/Team/Enterprise sub, an OpenAI account for codex, an OpenCode-supported provider, etc.
git clone https://github.com/hogu-dev/aias.git ~/repo/aias
cd ~/repo/aias
sudo ./install.shaias doctor # check prerequisites
claude setup-token # one-time host auth (if not already done)
# codex login # optional: enable codex
# opencode auth login # optional: enable opencode
cd ~/projects/my-project
aias init --runtime docker # also: runsc (gVisor) | kata-clh (Kata + Cloud Hypervisor)
# creates .aias/{config.toml, Dockerfile, compose.yaml, config.local.toml}
# interactively prompts: DinD? [y/N] (default no)
aias start # builds base + sandbox image on demand, then up -d
aias claude # drop into Claude inside the sandbox
aias stop # state in named volumes persistsaias supports three sandbox runtimes per sandbox. Pick at init via
--runtime <name>; switch later with aias rebuild --runtime <new>.
| Runtime | Isolation | Cold start | FS perf | When to pick |
|---|---|---|---|---|
| docker | Namespace + cgroups | instant | native | Default. Lowest install friction. |
| runsc | Software kernel (gVisor) | instant | ~10× slower for npm-install-style workloads | Stronger isolation than Docker without microVM overhead. NOTE: --dind not supported. |
| kata-clh | Hardware virtualization (Kata + Cloud Hypervisor) | ~200ms VM boot | ~3× slower for small-file ops (virtio-fs) | Strongest isolation. Requires KVM + Kata installed + a kata runtime alias in /etc/docker/daemon.json. |
aias doctor shows which are installed locally; missing ones come with install hints.
Note for Kata users: the aias name
kata-clhis auto-mapped to whatever Kata-related alias Docker has registered (kata-clh,kata, or anykata-*). If you've configured Kata to use QEMU or Firecracker as the hypervisor instead of Cloud Hypervisor, aias still works — the VMM choice is Kata's internal config, not an aias-level setting.Important for kata-clh on Docker 24+: register the runtime with the
runtimeTypefield (camelCase) indaemon.json, NOTpath. See docs/HOSTING.md for the full install runbook. Thepathregistration causes Docker to wrap the shim asio.containerd.runc.v2, which passes-rootthat Kata 3.x shims reject (shim exits status 2 with an unhelpful "log.json: no such file" error).
For
kata-clh+ DinD on hosts with empty removable block devices (e.g., built-in USB SD card readers with no card inserted), aias v0.3.1+ ships a wrapper runtime that strips host devices from the OCI spec. Without this, kata-clh+DinD startup crashes on empty-medium devices. See docs/HOSTING.md for details.
runsc+dindis rejected at init. gVisor doesn't support inner dockerd. Pickdockerif you need DinD, or accept the no-DinD limitation under runsc.
Cross-sandbox networking (
aias-net) under non-Docker runtimes is unverified. v0.2.x's external Docker networkaias-netis known to work underruntime = docker. Underrunscandkata-clh, the bridge / CNI behavior may differ. If you rely on multiple sandboxes reaching each other by name, test it explicitly for your runtime — and file an issue if you hit a regression. (Tracked for v0.3 verification.)
| Command | What |
|---|---|
aias doctor |
Prerequisites + state check (Docker, network, base images, host-auth status per agent) |
aias bootstrap |
Optional warm-up: state dir + network + base image |
aias bootstrap --rebuild |
Force-rebuild aias:base (run after git pull-ing aias) |
aias init --runtime <docker|runsc|kata-clh> [--dind|--no-dind] [--name NAME] |
Create .aias/ for the current dir |
aias start |
Build (if needed) + start sandbox |
aias stop |
Stop sandbox; volumes preserved |
aias status |
Compose ps for this sandbox |
aias shell |
bash inside the sandbox |
aias code |
Open VS Code attached to the running sandbox (Dev Containers) |
aias <agent> [args...] |
Run claude/codex/opencode inside the sandbox |
aias rebuild [--no-cache] |
Force-rebuild this sandbox's image |
aias dind on|off |
Toggle DinD; rebuild happens on next aias start |
aias share <bare|skills|mirrored>, aias sync <live|snapshot> |
Change share mode / sync strategy (restart to apply) |
aias gpu <all|none|indices> |
Grant host NVIDIA GPU(s) to the sandbox (CDI) |
aias regen |
Regenerate .aias/compose.yaml from config.toml |
aias image push|pull [ref] |
Share a prebuilt aias:base via your own registry |
aias destroy [--purge-volumes] |
Remove the sandbox |
aias list |
List known aias sandboxes |
aias prune |
Remove orphaned per-sandbox volumes + reclaimable image tags |
aias gc |
Drop this project's superseded image tags |
aias does NOT manage agent credentials — it just reflects what's on your host. Each agent has its own auth command you run once:
- claude:
claude setup-token(browser flow → writes~/.claude/.credentials.json) - codex:
codex login(browser flow → writes~/.codex/auth.json) - opencode:
opencode auth login(browser flow → writes~/.local/share/opencode/auth.json)
aias start checks each enabled agent's host credential file. If
missing, you get a per-agent hint:
[aias] ✗ claude: not authenticated on host
[aias] → run: claude setup-token
When you rotate your host auth (e.g., the OAuth token expired after a
year and you re-ran claude setup-token), restart your sandbox once
so the new credentials take effect:
aias stop && aias startThis is a caveat of Docker's file-bind-mount semantics (kernel pins the inode at mount time, so atomic file replacements on the host don't propagate to an already-running container). Rare enough that the friction is small.
Credentials staging (v0.2.4+): aias stages your host credentials into ~/.config/aias/sandbox-creds/<sandbox>/<agent>/<filename> on every aias start. The sandbox bind-mounts this directory (read-only) and aias-init symlinks each staged file at the agent's native path. This replaces v0.2.3's per-file mounts (which break under Kata's virtio-fs). Same rotation UX as v0.2.3: aias stop && aias start after rotating credentials on the host.
DinD off (default): Sandbox image builds FROM aias:base (no Docker CE,
no --privileged). Reduced attack surface. Good for most projects.
DinD on: Sandbox image builds FROM aias:base plus a Docker CE topping in
the per-sandbox .aias/Dockerfile. Container runs --privileged so the inner
dockerd works. Inner image state persists in the aias-dind-<sandbox> named
volume across sandbox restarts. Flip with aias dind on|off.
.aias/config.toml has a [secrets] block. Defaults:
| Default | What it does | When to disable |
|---|---|---|
gitconfig = true |
Mounts ~/.gitconfig:ro — agent uses your name/email |
Probably never |
ssh_agent = true |
Forwards $SSH_AUTH_SOCK:ro — agent can use ssh-added keys |
If you don't want the sandbox signing SSH ops |
[secrets.ssh_keys] |
Forward-only scoped agent: only the listed keys, each ssh-add -h destination-constrained; key files never enter the sandbox; works under docker and kata-clh |
Use instead of ssh_agent when you want selective SSH (specific keys, specific hosts), or SSH forwarding under kata-clh |
gh_cli = false |
Bind-mounts ~/.config/gh:ro (GitHub PAT) |
Opt in if you want gh inside |
aws = false |
Bind-mounts ~/.aws:ro (AWS keys) |
Opt in carefully — IAM scope = whatever you granted |
extra = [] |
Per-file mounts from ~/.config/aias/sandbox-secrets/<name>/ |
Curated, per-sandbox |
Per-machine overrides go in .aias/config.local.toml (gitignored). Example:
[secrets]
aws = true # enable on this machine onlyHost-wide defaults for every sandbox go in ~/.config/aias/config.toml (e.g.
[base] on_stale = "auto"); precedence is global < .aias/config.toml <
.aias/config.local.toml, so a project's own config always wins. See
docs/HOSTING.md.
Pick what host state appears in your sandbox:
aias init --runtime docker --share bare # isolated (default)
aias init --runtime docker --share skills # capabilities only
aias init --runtime docker --share mirrored # full mirrorAnd how it syncs:
aias init --runtime docker --share skills --sync live # bind mount (default)
aias init --runtime docker --share skills --sync snapshot # copy at startSee docs/share-modes.md for the trade-offs.
[secrets.extra]
OPENAI_API_KEY = { file = "~/.env", key = "OPENAI_API_KEY" } # env var
gcp-creds = { file = "~/.config/gcloud/credentials.json" } # file mountLegacy form extra = ["filename"] (v0.2.x) keeps working. See docs/share-modes.md.
- Unit tests (fast, mock Docker):
bats tests/unit - Integration tests (bring up real bench with DinD + gVisor):
./run-integration.sh. First run takes ~5 min to build the bench image; cached re-runs < 30 s. Covers runtime=docker + runtime=runsc + staging + rebuild paths end-to-end. (runtime=kata-clh currently skips in the bench due to an upstream Kata/Docker shim issue — see docs/TESTING.md for details.)
Building aias:base takes a couple of minutes. To build it once and reuse it
on your other machines, push it to a registry and have the others pull it.
aias never runs a registry — bring your own. A local one is a one-liner
(localhost:5000 is TLS-exempt, so no docker login and no daemon config):
docker run -d -p 5000:5000 --restart always --name aias-registry registry:3
On the builder machine:
# point at the registry (project .aias/config.toml or ~/.config/aias/config.toml):
# [image]
# base_registry = "localhost:5000"
aias image push # builds aias:base if needed, pushes localhost:5000/aias:base
On every consumer machine, set the same base_registry. Now aias start
pulls aias:base instead of building it. If the registry is unreachable, aias
falls back to a local build (with a warning) — so it works with or without one.
aias image pull # optional: refresh the local aias:base from the registry
aias image push <ref> # push an arbitrary, registry-qualified local image
aias image pull <ref> # pull an arbitrary ref
For a LAN registry (hostname:5000 instead of localhost), Docker requires TLS
or an insecure-registries entry in your daemon.json — that's your Docker
daemon's setup, not aias's. Auth (for ghcr/ECR/etc.) uses your existing
docker login; aias never handles registry credentials.