1 unstable release
Uses new Rust 2024
| new 0.1.0 | Mar 9, 2026 |
|---|
#68 in Development tools
340KB
7K
SLoC
A standard way to declare and run agent containers for your projects. Clone a repo, run one command, and talk to a fully configured AI agent:
- Zero setup:
agentcontainer runbuilds and launches a container with every tool your agent needs, as declared by the project maintainer. - Composable: Your local configuration merges with the project's, so you keep your own agent harness and credentials while inheriting project-specific tooling.
- Reproducible: The container is the environment. Every contributor gets the same agent setup, every time.
Quick start
cargo install --locked agentcontainer
git clone git@github.com:your-org/your-project
cd your-project
agentcontainer run
# Talk to the LLM.
Installation
cargo install --locked agentcontainer
Configuration
agentcontainer reads configuration from the following sources, listed from
lowest to highest priority:
| Source | Path |
|---|---|
| XDG global config | ~/.config/agentcontainer/config.toml |
| Home config | ~/.agentcontainer/config.toml |
| Ancestor configs | {ancestor}/.agentcontainer/config.toml for each ancestor directory from / towards the current working directory (excluding the CWD itself). Closer to / has lower priority. |
| Project config | .agentcontainer/config.toml |
| Local project config | .agentcontainer/config.local.toml |
| Environment variables | AGENTCONTAINER_<KEY> |
| CLI arguments | --<key> flags |
Configuration keys
| Key | Default | Description |
|---|---|---|
dockerfile |
.agentcontainer/Dockerfile |
Path to the Dockerfile. |
build_context |
. |
Directory used as the Docker build context. |
build_arguments |
(empty) | Extra --build-arg flags for docker build. See Build arguments. |
pre_build |
(empty) | Paths to executables to run before docker build. See Pre-build hooks. |
project_name |
Last component of the current directory, slugified | Name used in the Docker image tag. |
username |
Current OS user (from whoami) |
Username embedded in the image tag. |
target |
(none) | Docker build --target. When set, appended to the image tag. |
allow_stale |
false |
Use an existing image if the build fails, instead of returning an error. |
force_rebuild |
false |
Rebuild unconditionally, bypassing the staleness check. |
no_build_cache |
false |
Pass --no-cache to docker build. |
no_rebuild |
false |
Skip the build entirely. Errors if no image exists yet. |
volumes |
(empty) | Host-to-container volume mounts. See Volumes. |
environment_variables |
(empty) | Environment variables for the container. See Container environment variables. |
pre_run |
(empty) | Paths to executables to run before docker run. See Pre-run hooks. |
force_rebuild and no_rebuild are mutually exclusive.
target can be unset by a higher-priority source by setting it to "!". This
suppresses a value inherited from a lower-priority source. For example, if a
global config sets target = "builder", a project config can override it with
target = "!" to build without a target. The same works via environment
variables (AGENTCONTAINER_TARGET="!") and CLI (--target "!").
dockerfile, build_context, and target must not be set to an empty string.
Setting any of them to "" is a validation error. pre_build and pre_run
entries must not be empty strings.
Path resolution for dockerfile and build_context
A leading ~ in dockerfile or build_context is expanded to the user's home
directory. Only ~ alone or ~/… is expanded; ~user/… and embedded tildes
are left untouched. Relative paths are resolved against the current working
directory. All paths are lexically normalized: . and .. components are
resolved and consecutive slashes are collapsed, without accessing the
filesystem.
Image naming
The Docker image tag is derived from the resolved configuration:
agentcontainer_<username>_<project_name>:latest
agentcontainer_<username>_<project_name>_<target>:latest # when target is set
username, project_name, and target are all slugified before being
embedded in the tag: lowercased, non-alphanumeric characters replaced with _,
consecutive underscores collapsed, and leading/trailing underscores trimmed.
username and project_name must contain at least one alphanumeric character.
If the slug of either value would be empty, get_config returns an error.
Build arguments
The build_arguments table defines extra --build-arg flags passed to
docker build. Values can be:
- A string: pass this literal value as a build argument.
true: inherit the value from the host environment variable with the same name.false: remove a build argument inherited from a lower-priority config source.
[build_arguments]
USERNAME = "alice"
BUILD_DATE = "2026-03-06"
HOME = true # inherit from host environment
OLD_ARG = false # suppress from a lower-priority source
On the CLI, use --build-arg (repeatable):
# Set a literal value.
agentcontainer build --build-arg USERNAME=alice
# Inherit from the host environment.
agentcontainer build --build-arg HOME
# Remove a build argument inherited from config files.
agentcontainer build --build-arg '!OLD_ARG'
Build argument keys must be valid POSIX identifiers: start with a letter or underscore, followed by ASCII letters, digits, or underscores.
Pre-build hooks
The pre_build option specifies a list of paths to executables that run before
docker build. Hooks form a pipeline: the first hook receives the hookable
arguments computed from the configuration (i.e. --build-arg entries), and
each subsequent hook receives the output of the previous one. The final result
is injected into the docker build command after all built-in flags but before
the build context. Built-in flags managed by agentcontainer (--file,
--target, --tag, --no-cache, build context) are not exposed to hooks.
Lists from multiple config sources are concatenated (lower-priority first). For
example, a hook defined in config.toml runs before one added via
config.local.toml or the CLI.
This provides a way to dynamically compute Docker build flags based on the host environment.
Note: Pre-build hooks run unconditionally whenever a build or run
command is invoked, even if the build is ultimately skipped (e.g. because the
image is already up to date or --no-rebuild is set). Keep this in mind if a
hook is expensive or has side effects.
Each hook:
- Receives the path to a temporary file as its first argument (
$1). The file contains the current arguments as a TOML document with anargskey (e.g.args = ["--build-arg", "FOO=bar"]). - Must print a TOML document with the same shape to stdout (e.g.
args = ["--build-arg", "FOO=bar", "--label", "version=1"]). - Must exit with status 0.
- Must produce valid UTF-8 output.
Example hook script (requires toml from toml-cli and jq):
#!/bin/sh
set -euo pipefail
input_file="$1"
# Read the current args from the input file.
existing=$(toml get "$input_file" args)
# Build extra entries.
extra=$(jq -n \
--arg a1 "--build-arg" --arg v1 "BUILD_DATE=$(date "+%Y-%m-%d")" \
'[$a1, $v1]')
# Merge and output.
merged=$(jq -cn --argjson existing "$existing" --argjson extra "$extra" \
'$existing + $extra')
echo "args = ${merged}"
A leading ~ in each path is expanded to the user's home directory. Only ~
alone or ~/… is expanded; ~user/… and embedded tildes are left untouched.
Relative paths are resolved against the current working directory. All paths
are lexically normalized: . and .. components are resolved and consecutive
slashes are collapsed, without accessing the filesystem.
In TOML configuration:
pre_build = ["~/hooks/pre-build.sh"]
pre_build = [
"scripts/pre-build.sh",
] # resolved relative to the current working directory
pre_build = ["hooks/a.sh", "hooks/b.sh"] # multiple hooks in one source
On the CLI (repeatable):
agentcontainer build --pre-build ~/hooks/pre-build.sh
agentcontainer build --pre-build hooks/a.sh --pre-build hooks/b.sh
Volumes
The volumes table maps container paths to host paths. In TOML, each key is a
container path and the value is either:
- A string: an explicit host path to mount at the container path.
true: mount at the same path in the container as on the host (the key is used as both host and container path).false: remove a volume inherited from a lower-priority config source.
A leading ~ in container-path keys and host-path values is expanded to the
user's home directory before config sources are merged. This means ~/.ssh and
/home/alice/.ssh from different sources are treated as the same volume during
priority resolution. Only ~ alone or ~/… is expanded; ~user/… and
embedded tildes are left untouched.
Relative host paths that look like filesystem paths (start with . or contain
/) are resolved against the current working directory. Plain names without a
. prefix or / (e.g. my_volume) are treated as Docker volume names and
left unchanged. All resolved paths are lexically normalized: . and ..
components are resolved and consecutive slashes are collapsed, without
accessing the filesystem.
[volumes]
"/workspace" = "~/projects/myproject"
"/data" = "/mnt/shared-data"
"/app" = "./src" # resolved relative to the current working directory
"~/.ssh" = true # mount at the same path inside the container
"/cache" = "my_cache_volume" # Docker volume name, left unchanged
"/unwanted" = false # suppress a volume defined in a lower-priority source
On the CLI, use --volume (or -v) (repeatable):
# Mount a host path into the container.
agentcontainer build --volume '~/projects/myproject:/workspace'
# Mount at the same path inside the container (same-path shorthand).
agentcontainer build -v /home/alice/.ssh
# Remove a volume inherited from config files.
agentcontainer build --volume '!/unwanted'
Container environment variables
The environment_variables table defines environment variables to pass into
the container. Values can be:
- A string: pass this literal value.
true: inherit the variable from the host environment.false: remove a variable inherited from a lower-priority config source.
[environment_variables]
EDITOR = "nvim"
SSH_AUTH_SOCK = true # inherit from host
OLD_VAR = false # suppress from a lower-priority source
On the CLI, use --env (or -e) (repeatable):
# Set a literal value.
agentcontainer build --env EDITOR=nvim
# Inherit from the host.
agentcontainer build --env SSH_AUTH_SOCK
# Remove a variable inherited from config files.
agentcontainer build -e '!OLD_VAR'
Variable keys must be valid POSIX identifiers: start with a letter or underscore, followed by ASCII letters, digits, or underscores.
Pre-run hooks
The pre_run option specifies a list of paths to executables that run before
docker run. Hooks form a pipeline: the first hook receives the hookable
arguments computed from the configuration (i.e. --volume and --env
entries), and each subsequent hook receives the output of the previous one. The
final result is injected into the docker run command after all built-in flags
but before the image name. Built-in flags managed by agentcontainer (--init,
--rm, --tty, --interactive, --user, --group-add, --name,
--workdir, the current-directory volume, the worktree volume, the image name,
and container arguments) are not exposed to hooks.
Lists from multiple config sources are concatenated (lower-priority first). For
example, a hook defined in config.toml runs before one added via
config.local.toml or the CLI.
This provides a way to dynamically compute Docker flags at runtime based on the host environment.
Each hook:
- Receives the path to a temporary file as its first argument (
$1). The file contains the current arguments as a TOML document with anargskey (e.g.args = ["--volume", "/host:/container"]). - Must print a TOML document with the same shape to stdout (e.g.
args = ["--volume", "/host:/container", "--network", "host"]). - Must exit with status 0.
- Must produce valid UTF-8 output.
Example hook script (requires toml from toml-cli and jq):
#!/bin/sh
set -euo pipefail
input_file="$1"
# Read the current args from the input file.
existing=$(toml get "$input_file" args)
# Build extra entries.
extra=$(jq -n \
--arg a1 "--network" --arg v1 "host" \
'[$a1, $v1]')
# Merge and output.
merged=$(jq -cn --argjson existing "$existing" --argjson extra "$extra" \
'$existing + $extra')
echo "args = ${merged}"
A leading ~ in each path is expanded to the user's home directory, just like
for volume paths and pre_build. Only ~ alone or ~/… is expanded;
~user/… and embedded tildes are left untouched. Relative paths are resolved
against the current working directory. All paths are lexically normalized: .
and .. components are resolved and consecutive slashes are collapsed, without
accessing the filesystem.
In TOML configuration:
pre_run = ["~/hooks/pre-run.sh"]
pre_run = [
"scripts/pre-run.sh",
] # resolved relative to the current working directory
pre_run = ["hooks/a.sh", "hooks/b.sh"] # multiple hooks in one source
On the CLI (repeatable):
agentcontainer run --pre-run ~/hooks/pre-run.sh
agentcontainer run --pre-run hooks/a.sh --pre-run hooks/b.sh
Container naming
When running a container, the name is derived from the project name and a random numeric suffix:
agentcontainer_<project_name>_<suffix>
The slugified project name is truncated to 41 characters (with any trailing underscore removed after truncation) so that the full container name never exceeds Docker's 63-character limit for container names.
Example configuration file
dockerfile = ".agentcontainer/Dockerfile"
build_context = "."
project_name = "myproject"
username = "alice"
[volumes]
"/workspace" = "~/projects/myproject"
"~/.ssh" = true # same path on host and in container
[environment_variables]
EDITOR = "nvim"
SSH_AUTH_SOCK = true
Environment variables
Each configuration key maps to an AGENTCONTAINER_<KEY> environment variable,
where <KEY> is the uppercase version of the key name. Values are parsed as
TOML. For example:
AGENTCONTAINER_DOCKERFILE=".agentcontainer/Dockerfile"
AGENTCONTAINER_BUILD_CONTEXT="."
AGENTCONTAINER_BUILD_ARGUMENTS='{BUILD_DATE = "2026-03-06", HOME = true}'
AGENTCONTAINER_PRE_BUILD='["~/hooks/pre-build.sh"]'
AGENTCONTAINER_ALLOW_STALE=true # or `false`
AGENTCONTAINER_VOLUMES='{"/workspace" = "~/projects/myproject", "~/.ssh" = true}'
AGENTCONTAINER_ENVIRONMENT_VARIABLES='{EDITOR = "nvim", SSH_AUTH_SOCK = true}'
AGENTCONTAINER_PRE_RUN='["~/hooks/pre-run.sh"]'
Commands
config
Print the resolved configuration, with all sources merged, as TOML.
agentcontainer config
build
Build the agent container Docker image.
agentcontainer build
The build is skipped if the image is already up to date. A rebuild is triggered when any of the following is true:
- No image exists yet.
- The Dockerfile was modified after the image was last tagged.
- The image was last tagged before the start of today (local time).
force_rebuildis set.
run
Run the agent container. The image is automatically built (or rebuilt if stale)
before starting the container. This replaces the current process with
docker run via exec.
agentcontainer run [-- <container-args>...]
Arguments after -- are passed through to the container's entrypoint.
Everything before -- is parsed by agentcontainer itself and will error on
unrecognized flags.
# Run interactively with no extra arguments.
agentcontainer run
# Pass arguments to the container entrypoint.
agentcontainer run -- --print --output-format json
# Global flags still go before the subcommand.
agentcontainer --project-name foo run -- --help
The build step honors all build-related configuration keys (allow_stale,
force_rebuild, no_build_cache, no_rebuild, etc.). If the image is already
up to date, the build is skipped and the container starts immediately.
The container is started with:
--init: the container uses an init process.--rm: the container is automatically removed on exit.- UID/GID mapping: the container runs as the current user and group, with
group
0added via--group-add. - Current directory volume: the working directory is mounted into the container at the same path and set as the container's working directory.
- Git worktree volume: if the current directory is a linked Git worktree, the main worktree root is also mounted so that Git objects are accessible.
- Configured volumes and environment variables: as defined in the configuration.
- TTY mode:
--tty(allocate pseudo-TTY) and--interactive(keep stdin open) are only added when standard input is a TTY. This means piped or scripted invocations won't cause Docker to hang or emit spurious warnings.
Example
This project is the example.
First, we have .agentcontainer/Dockerfile. It
installs all the tools the agent will need to interact with this project during
development. For example:
docker, so the agent can interact with the development container.gitandghto interact with Git history and GitHub PRs.- Prepares the container with the current user's ID and group ID to avoid permission problems.
- Mechanisms to bust image caching to easily update the agent harness periodically.
- Install an agent harness (Claude Code).
And the .agentcontainer/config.toml file
configures scripts that allow customization of Docker build arguments and
Docker run arguments.
- The
pre_buildhook takes care of passing the current user's ID and group ID to the image, and today's date to bust the cache daily to update the agent harness. - The
pre_runhook takes care of exposing the right Docker socket for Docker-in-Docker functionality.
agentcontainer always mounts the project into the container, and it
automatically detects if it is in a Git worktree and, if so, makes sure to also
mount the main worktree to make sure Git commands will be available to the
agent.
The CLAUDE.md tells the agent about the development tools it has
available (one to run the linters, and one to run the tests), and those tools
take care of everything for the agent (building the development container and
running it if needed).
The objective is that all you have to do is download the project, run
agentcontainer run, and start talking to the LLM. Describe the change you
want to make, and off it goes all the way to making a commit. If you leverage
the flexible configuration from above by sharing a GH_TOKEN, it could even
push the PR for you.
The pre_build and pre_run scripts could do anything you need. For example:
- Dynamically customize your
~/.claude/settings.jsonfile depending on each project's needs, push the settings to a temporary file, and mount that temporary file into the agent container. - Fetch your LLM provider API key from your secret manager (1Password,
LastPass, BitWarden,
age) and inject it into the agent container via--envor--env-file. - Fetch your GitHub token and inject it into the agent container as
GH_TOKEN.
Roadmap
in_buildhooks: Executables to be injected into the build context and executed during the build process to allow customizing the image itself.post_runhooks: These hooks will receive the raw output of the AI harness, processes it, and outputs what should be printed to the user instead of the Ai harness output. Useful in print mode with raw JSON output, for example.
Contributing
- Fork this project.
- Download it locally.
- If you want assistance from Claude Code, run
agentcontainer run. - Make your changes.
- Push your changes to your forked repository.
- Create a PR.
License
This project is licensed under the MIT license.
Contributions
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this repository by you shall be licensed as MIT, without any additional terms or conditions.
Dependencies
~12–22MB
~349K SLoC