You can read more about the rationale behind this project in Pedro Freire's blog post "Safer & Faster: AI in an Apple Container"
Are you a proud macOS 26+ user on Apple Silicon wanting to run Apple Container to sandbox your AI agents? Then this project is for you.
Container-Agents is a macOS-native development environment for running multiple coding agents autonomously while preserving the developer's existing workflow—including local VMs and controlled remote-server access.
Container-Agents gives each coding agent its own disposable Linux workspace while still feeling identical to your normal terminal workflow. Prior to using this project you'd launch Claude Code with:
cd project
claudeAfter this project is installed, you launch Claude Code with:
cd project
claudeTwo major advantages in the second launch, though:
- Claude Code will only have access to files and directories inside
project, under the exact same absolute path. - If "project" is a configured project directory, Claude Code runs with
--dangerously-skip-permissionsand therefore asks no permissions before making changes to files.
Safer. Faster.
This project assumes coding agents are useful enough to deserve real tools, and powerful enough to deserve boundaries.
By default, the current directory is the only project mount, using the exact same
absolute path in the container, as the host.
As a precaution, if the current directory is your home (~) or main documents root
(~/Documents), the script will ask you if you're really trying to share
all your documents with the agent.
Inside trusted project paths, agents get their normal autonomous flags
(e.g., --dangerously-skip-permissions) because that is where you
expect them to work. Host-localhost access and VM access are opt-in.
Per-agent-SSH is opt-in per invocation.
Images are disposable, but the agent's configuration and state is persisted
across invocations via explicit mounts.
That combination removes hesitation from an AI workflow: agents can do serious engineering work without getting a free pass to your whole Mac.
Container-Agents supports the MCP configuration and plugins you prefer:
just be sure to edit the sample Dockerfile for your agent, to ensure
your favorite setup is pre-installed on the container.
You can also have the container talk to MCP servers hosted on the Internet, as usual, or to MCP servers running on the local host.
You could install a platform for running local LLMs (Ollama, LM Studio, MTPLX) inside the container, but the fixed memory limit of a container isn't well suited for a large memory footprint service such as an LLM platform.
Container-Agents supports connecting its agents to LLM platforms running
in the local host. Learn more about option ---local.
Container-Agents (optionally) supports connecting its agents to virtual machines running on
the local host, and to map agent-specific SSH configuration with the container.
Learn more about options ---vm and ---ssh.
What stands out in Container-Agents is it's opinionated nature about disappearing underneath the developer's existing
environment. You type claude; your IDE passes the same absolute paths; your existing VM hostnames still work;
your existing servers remain accessible under deliberately constrained identities.
Find a quick comparison below.
| Capability | Container-Agents | Docker Sandboxes | Zigotica's Agent Sandbox | Mattolson's Agent Sandbox |
|---|---|---|---|---|
| Autonomous coding agents | Designed for autonomous coding CLIs, with unsafe flags enabled only inside trusted project paths. | Possible, but usually assembled per project or per CLI. | Focused on sandboxing an agent workflow; agent support depends on its harness. | Focused on agent sandboxing; agent support depends on its configured workflow. |
| Multiple agent harnesses | Built around wrapper scripts for Codex, Claude Code, Gemini, Antigravity, Vibe, and OpenCode. | Usually one-off images or compose files unless you build a shared launcher layer. | Primarily one sandbox harness. | Primarily one sandbox harness. |
| Strong VM isolation | Uses Apple Container, which runs Linux containers inside Apple's lightweight VM isolation model. | Docker Desktop also uses a Linux VM on macOS, but behavior depends on Docker's file sharing and networking model. | Docker-based isolation. | Docker-based isolation. |
| Only expose project | ✓ | Commonly possible with bind mounts, but depends on each setup. | Project exposure depends on how the sandbox is launched. | Project exposure depends on how the sandbox is launched. |
| Current directory by default | ✓ | Possible, but typically requires custom scripts or aliases. | Depends on the harness invocation. | Depends on the harness invocation. |
| Preserve identical absolute host path | ✓ | Usually not by default; many setups mount to /workspace or another container-only path. |
Typically uses a container workspace path. | Typically uses a container workspace path. |
| Host-local services | ✓ | Possible through Docker host networking conventions, with platform-specific caveats. | Depends on Docker networking configuration. | Depends on Docker networking configuration. |
| Network access policies | Explicit flags control localhost, VM access, SSH material, and raw/safe agent startup. | Docker network policy is flexible, but usually left to the caller to define. | Depends on the sandbox configuration. | Depends on the sandbox configuration. |
| Local development VM integration | ✓ | Possible manually with Docker networking or host tunnels. | Not a primary feature. | Not a primary feature. |
| Per-agent SSH identities | ✓ | Possible manually, but not usually a built-in convention. | Depends on configuration. | Depends on configuration. |
| Remote-server agent attribution | ✓ | Possible if you create separate keys and mount them carefully. | Depends on configured SSH identity handling. | Depends on configured SSH identity handling. |
| Trusted-directory -> autonomous mode | ✓ | Usually manual per invocation. | Depends on the harness policy. | Depends on the harness policy. |
| Native Apple Containers | ✓ | No | No | No |
| Cross-platform | No | ✓ | ✓ | ✓ |
- macOS with Apple Container installed and working.
- Enough local resources for the default container run settings:
--cpus 2 --memory 2G. - Agent credentials or login state for whichever CLI you want to use.
sudoaccess for Apple Container DNS helpers and VM tunnel helpers.
Build the images:
cd src/containers
bash build-all.shMake the launchers easy to call.
Copy/symlink all src/* Bash scripts to a location in your PATH
and enable their executable permission.
chmod a+x src/*
export PATH="$PWD/src:$PATH"From any project directory, start an agent:
codex
claude
antigravity
agy
gemini
vibe
opencodeBe sure to run your agents with ---link once to soft-link the
src/containers/<agent>/!* directories and files to their proper
~/.config and ~/.local, etc., locations.
The wrappers are tiny scripts that all delegate to src/agent-start.
You can also call the main launcher directly:
agent-start ---agent=codexThe defaults live near the top of src/agent-start, but local machine settings
can be overridden with ~/dev-env.sh. This repository includes dev-env.sh as a
template that you can copy to that location:
DEV_ENV_PROJECT_DIRS=(
"$HOME/Documents/Software"
)
# Base directory for container settings
# Note: This should be an absolute path! Replace before running!
DEV_ENV_CONTAINERS_DIR="./src/containers"
DEV_ENV_LOCALHOST_DNS_ALIAS=host.container.internal
DEV_ENV_LOCALHOST_IP_ALIAS=203.0.113.113
DEV_ENV_VM_USER=user
DEV_ENV_VM_HOST=vm.local
DEV_ENV_VM_PORTS=(22 80 443)The most important setting is PROJECT_DIRS/DEV_ENV_PROJECT_DIRS.
When your current directory is inside one of those paths, agent-start considers it trusted and
enables the agent's unsafe or fully-autonomous flags. Outside those paths, only the current
directory is mounted at <agent-home>/workspace, and the unsafe flags are not
added.
Runtime CLI configuration is mounted per agent in src/agent-start. Adjust those
volume paths to match where you keep each agent's config, auth, and session
state. The checked-in Dockerfile.dockerignore files intentionally exclude
logs, caches, histories, sessions, and auth databases so they do not become part
of built images.
There are pre-installed tools in each container. Be sure to edit your agent's config (initially empty) to add support for:
- Microsoft's Playwright-CLI
- If using shared skills (i.e.,
AI_SKILLS_DIRis not empty), then shell into any container (e.g.,claude ---sh) and typeplaywright-cli install --skills=agents -g - If not using shared skills:
- In Codex, type
$skill-installer playwright - In Claude, shell into the container (
claude ---sh) and typeplaywright-cli install --skills - In Antigravity, Gemini, Vibe: use the same method as for shared skills
- OpenCode should auto-detect Playwright-CLI.
- In Codex, type
- If using shared skills (i.e.,
Run from the project you want the agent to edit:
cd ~/Documents/Software/my-project
codexPass normal CLI arguments to the agent:
codex "explain this repository"
claude "run the tests and fix the first failure"Use --- when you want to stop processing further arguments and pass the remaining ones to the agent,
unchanged. This is optional: if ALLOW_TRIPLE_DASH_OPTS_ONLY is true (default), all arguments
recognized by the agent-start wrapper start with ---, and any arguments not recognized will be
passed to the agent.
Show all launch options:
codex ---helpUsage:
opencode [<options>, ...]
Options:
---agent=<agent> Select the agent to use (mandatory).
Supported agents: claude, codex, antigravity, agy, gemini, vibe, opencode, n8n.
---all Map all project dirs ($PROJECT_DIRS) to allow the agent to work across
projects.
---sh Run the container shell, not the agent itself.
Must be defined after ---agent.
---ssh Map ~/.ssh/* files to allow the agent to access to servers/Virtual Machines.
---local Create a container DNS 'host.container.internal' that maps to the host's localhost,
via 203.0.113.113. Required to access Ollama, LM Studio, MTPLX models,
VMs, or other services running on the host. This DNS entry is persistent across
all future invocations of agents, until a reboot. Environment vars
LOCALHOST_DNS_ALIAS and LOCALHOST_IP_ALIAS are passed to the agent for reuse.
This is incompatible with iCloud Private Relay.
---local-del Delete DNS entry created by ---local, and exit.
---vm Setup SSH tunnels to allow the agent to access a host VM ('user@vm.local')
using Shared Network. Not required for Bridged Network.
This SSH tunnel is persistent across all future invocations of agents, until
the VM shuts down. Implies ---local.
This is incompatible with services running in the host in the same ports
(22 80 443).
---vm-del Close the SSH tunnel created by ---vm, and exit. Does not do a ---local-del.
---connect Combined ---ssh, ---local and ---vm.
---link Link './src/containers/<agent>/!*'
to ~/.config, ~/.local, etc. and exit.
---unlink Unlink './src/containers/<agent>/!*'
from ~/.config, ~/.local, etc. and exit.
---safe Do not pass any '--dangerously-skip-permissions'-type options to the agent.
Must be defined after ---agent.
---raw Do not pass any implicit options or environment to the agent at all.
Must be defined after ---agent.
---help This help.
All ---local, ---vm also create DNS aliases inside the container, that match host /etc/hosts DNS
aliases pointing to 127.0.0.1 or the VM's 'vm.local' IP. All DNS aliases inside the container will
point to 203.0.113.113. This ensures they match functionality on the host and container.
Pass options to the agent:
--- <agent-options> Stop processing the next options and pass them to the agent.
- Runs popular AI coding CLIs in Apple Containers:
- OpenAI Codex / ChatGPT CLI
- Claude Code
- Google Gemini CLI
- Google Antigravity CLI
- Mistral Vibe
- OpenCode
- Mounts only the current directory by default.
- Enables full agent permissions only inside trusted project directories.
- Optionally mounts all configured project roots so agents can work across related repositories.
- Shares a single AI skills directory ($AI_SKILLS_DIR) across all agents (BETA: this shares only the directory, doesn't change the format; set $AI_SKILLS_DIR empty to disable)
- Optionally mounts agent-specific SSH keys and known hosts.
- Optionally exposes host
localhostto containers through a stable DNS alias, useful for Ollama, LM Studio, local model servers, web apps, databases, and other development services. - Optionally creates SSH tunnels to a host VM — required for VM "Shared Networking"
- Automatic support for ACP via auto-detection of TTY.
- Keeps runtime auth/session/log state out of images; the images contain tools and starter config, while state is persisted across invocations via mounts.
- Soft-links persisted state to their expected locations in
~. - Uses small wrapper scripts, so daily use can be as simple as
codexfrom the directory you already work in.
Each agent's Dockerfile creates a specific OS user for the agent to run under.
This user has the same name and home directory as the user you're running under
in macOS -- thereby ensuring matching absolute paths between host and container,
making it easier to share configuration and files between both.
Apple Containers launches each container in a virtual network segment separate from
the host's localhost. No default routing exists between the two.
Apple
provides a solution,
though. ---local creates an Apple Container DNS entry so the container can reach a host
service through host.container.internal. This is useful for local LLM model
servers, web apps, databases, and API mocks.
Localhost aliasing is optional because it is incompatible with iCloud Private Relay.
Virtual Machines (VMs) can be configured with both Shared or Bridged Network. With Apple Virtualization, Shared Network has the advantage that if you take your laptop to a location that has no network access, your host system can still network with the local VM. Bridged Network cannot — at least not easily.
However, Shared Networking brings a new hurdle: Apple creates the IP for the VM under a separate virtual network segment from Apple Containers. No default routing exists between the two.
To enable this communication, Container-Agents has the ---vm option which
creates SSH port forwards from the host to a configured VM. Using this option
also enables ---local so the container can reach the host's localhost network.
Custom DNS hosts defined in /etc/hosts of the host system are common in
developer systems. They point either to locally-running services
(LLMs, MCPs, databases), or to VM services.
When using either ---local or ---vm, all relevant /etc/hosts names are copied
to the container system, with all of them pointing to
LOCALHOST_IP_ALIAS/DEV_ENV_LOCALHOST_IP_ALIAS. This ensures those names
are seamlessly available to the agent, as if it were running on the host.
src/vm-refresh-ip can help update /etc/hosts when a VM keeps the same SSH
host key but receives a new IP address.
---ssh mounts agent-specific SSH material into the container. The current
convention expects files like:
~/.ssh/ssh-agent-codex.pem
~/.ssh/ssh-agent-codex.pem.pub
~/.ssh/config-agent-codex
~/.ssh/known_hosts_agents
Each agent has its own key/config name. This keeps an agent's server access separate from your normal host SSH setup and makes it easier to revoke or rotate. It also ensures all agent actions on the server(s) are traceable.
Some IDEs (e.g.: Visual Studio Code, JetBrains' IDEs) may install local copies
of your agents.
To have the configuration of such agents in sync with their container version,
while you experiment, you can invoke each agent with ---link to soft-link the
agent-relevant ~/.config, ~/.local, etc., files to the main
src/containers/<agent>/!* locations.
Use this sample JSON with JetBrains' IDEs to invoke OpenCode in a container:
{ "default_mcp_settings": {},
"agent_servers": {
"OpenCode in container": {
"command":"/<path-to->/opencode",
"args": ["---safe", "acp"]
}
}
}Found in src/containers.
Each container image follows the same broad structure:
almalinux:10-minimalbase image.- Common development tools such as Git, OpenSSH client, curl, rsync, tar, gzip,
unzip, compiler tooling,
jq,yq,nc, and Node.js 22. - An unprivileged user.
- Agent-specific starter config copied into that unprivileged user's home.
- Agent-specific runtime config mapped from
!*files and directories — their standard leading dot (~/.*) replaced with!to make it easily visible and editable in macOS Finder. - The selected AI CLI installed during image build.
bootstrap-hosts.shas the entrypoint, which appends host aliases and then drops privileges to the unprivileged user before running the CLI.
To add tools that your agents commonly need, edit the relevant Dockerfile under
src/containers/<agent>/Dockerfile and rebuild that image.
- Add
src/containers/<agent>/Dockerfileandbuild.sh. - Add starter config under
src/containers/<agent>/, excluding runtime secrets inDockerfile.dockerignore. - Add a wrapper script in
src/. - Add a case to
src/agent-startwith the container image name, environment variables, executable, safe arguments, and unsafe arguments. - Add a container-run block with any agent-specific config and SSH mounts.
- Rebuild and test with
---shbefore running the agent normally.
Author: Pedro Freire
License: Apache 2.0