- Shell 97.7%
- Dockerfile 1.1%
- JavaScript 0.8%
- Makefile 0.4%
On Linux a launch rebuilds the client image whenever the nightly base changes, and every layer of a workspace Dockerfile built on it rebuilds with it. Show the multi-stage shape that keeps the heavy work in a stage independent of the client image, and that podman image prune drops that stage's cache, as a real Podman 5.7.0 build showed. Refs #74 |
||
|---|---|---|
| .agents | ||
| .claude | ||
| .codex | ||
| .forgejo | ||
| .opencode | ||
| bin | ||
| clients | ||
| containers | ||
| docs | ||
| examples | ||
| lib | ||
| tests | ||
| .gitignore | ||
| .prettierrc | ||
| .shellcheckrc | ||
| AGENTS.md | ||
| COPYRIGHT | ||
| install.sh | ||
| Makefile | ||
| README.md | ||
azkaban
azkaban runs Codex CLI, Claude Code, or OpenCode inside separate
containers, rootless Podman on Linux and Apple container on macOS,
while sharing one audited host launcher. The public commands are
codex, claude, and opencode; each keeps its own image,
configuration, authentication, and persistent state.
An agent left to work without approval prompts can, on the host, read
your keys, change files outside the project, and push as you. azkaban
lets it work that way inside a container that sees the project folder,
the client's own state, and only what you expose
(Security model). It also keeps who acts apart: each
launch runs as an identity with its own keys, home files, and
instructions, and each client login lives in a state folder of its own,
so personal, work, and bot sessions keep their credentials and history
apart unless you configure them to share.
Every client image builds on Ubuntu 26.04 LTS with git, Node.js,
ShellCheck, Prettier, the forge CLIs gh and fj, and common
command-line tools. containers/Dockerfile,
the installer's image definition, lists the rest; a project adds its own
tools through a custom image.
Quick start
On Linux with Bash 4.4 or newer, get azkaban, install a client, put the
launcher on PATH, and start it in a project folder that holds nothing
the agent must not read:
git clone https://git.unitoo.it/claudiomaradonna/azkaban.git
cd azkaban
./install.sh claude
export PATH="$HOME/.local/bin:$PATH"
cd ~/projects/example
claude .
The installer installs rootless Podman when it is missing. Log in
through the client on its first launch, or put its API key in
~/.azkaban/identities/default/env, as
Set up the default identity
describes. Installation covers the other
clients, the install options, and updates, and macOS,
not yet verified on a real Mac, the setup there.
Features
- Identities: who acts in a launch,
with its API keys, SSH key, home files, and goals;
--identity NAMEpicks one,defaultotherwise. - Accounts: separate logins of one
client, such as a personal and a company subscription, with
--account NAME. - Bot accounts: agents that sign and push under a forge account of their own.
- Home files: tool configuration, such as a gitconfig, mounted read-only in every launch's container home.
- Custom images: a workspace
Dockerfile.<client>orDockerfile.azkabanadds tools, built only after you approve it. - Environment files, extra mounts and networks, and shared MCP binaries.
- Nested sandboxes with
bwrapinside a Linux container. - Dry run and a headless contract for scripts and supervisors.
Usage
The first positional argument is the workspace when it is an existing
folder, and must precede native-client arguments. Use -- to stop
wrapper parsing explicitly:
codex .
claude ./project -- --native-option
opencode ./project -- run --auto "Explain the test suite"
opencode ./project -- --session SESSION_ID
Any other first argument goes to the client, and the workspace is then
the current directory: a terminal asks you to confirm it, and a launch
whose stdin is not a terminal uses it unasked. A mistyped folder name,
as in claude ./projct, therefore mounts the whole current directory.
Every command accepts these options:
| Option | Purpose |
|---|---|
-h, --help |
Show command help. |
--network NAME |
Attach to an existing network. |
--mount SRC:DST[:OPTIONS] |
Add a bind mount; repeatable (Extra mounts). |
--max-memory VALUE |
Set the container memory limit; without one, Podman sets none; on macOS see macOS. |
--image NAME |
Override the client image. |
--skip-custom-images |
Run the image without building a workspace Dockerfile (Custom images). |
--unmask-proc |
Lift the /proc masks for a nested sandbox; Linux only. |
--cidfile PATH |
Write the container ID to a host file outside the workspace. |
--identity NAME |
Run as an identity; default when omitted. |
--account NAME |
Keep the client's state in an account instead of the identity's. |
--no-tty |
Disable TTY allocation; without it, a TTY is allocated when standard input and output are terminals. |
--dry-run |
Print one shell-escaped launch command without launching (Dry run). |
-- |
Forward all remaining arguments to the native client. |
Use codex --help, claude --help, opencode --help, or
./install.sh --help for the current command surface.
Security model
Codex and Claude run with their native approval checks disabled because
the container is the intended containment boundary. Those bypass options
are not safe to use on the host. OpenCode keeps its native
permission rules by default; pass run --auto explicitly for unattended
work. OpenCode's --auto approves requests while preserving explicit
deny rules. On Linux a rootless container reduces host access, but it is
not a virtual machine. On macOS each container runs in its own
lightweight virtual machine without a user namespace, so root in the
container is root in that machine. Neither is a defense against every
kernel or container-runtime vulnerability.
By default the client can access its workspace, its client-specific
persistent state, and the resources you explicitly expose, and it runs
at most 512 processes, a fixed limit. It sees the whole workspace, so
keep any file it must not read outside that folder. Everything the
launcher keeps on the host lives below ~/.azkaban, and the client's
state directory, ~/.azkaban/state/<identity>/<client> for the
identity the launch runs as, or
~/.azkaban/accounts/<account>/<client> for an
account it names, is the only part of
it the client can write. Extra mounts, an identity's SSH key or
host agent, networks, and the
/proc unmask expand or alter
that boundary; review each of them as security-sensitive configuration.
On Linux amd64 and arm64 the client image builds on your host on a base
image the project's forge builds and publishes, which each launch pulls
anew (Updates). A workspace
Dockerfile.<client> or Dockerfile.azkaban changes the image only
after you approve its content (see
Custom images). azkaban never mounts the
engine's socket. When agents need to sign and push, give them a bot
account of their own, as described in
Run agents as a bot account.
The launcher parses environment files as data: it does not source them
or use eval. File-provided container variables reach the container
through a private env file that the engine reads, so their values never
enter the environment of a program the launcher runs on the host, nor
any process argument list. The workspace .env.<client>, which the
client can write, holds container variables only (see
Environment configuration).
Trusted adapters are loaded only from the installed runtime, never from
a workspace, PATH, or configuration file.
A tool inside a Linux container can isolate folders with bwrap
(Nested sandboxes), which
keeps a cooperative command to its folders but cannot hold one crafted
to escape it; the container stays the boundary toward the host.
Documentation
Beside the pages the features link:
- Installation: prerequisites, install options, updates, and uninstalling.
- macOS: setup and how launches differ.
- Troubleshooting: common errors and their fixes.
- Development, Adding a client, and Publishing: working on azkaban itself and publishing its base image.
License
COPYRIGHT holds the license and attribution details.