Shell shortcuts¶
Extra Docker options can defeat containment
The user launching the container is trusted and is not the threat this repository's containment model is designed to control. The coding agent and model running inside the container are the untrusted workload.
Nevertheless, if the user intentionally or unintentionally supplies an
unsafe option through --docker, that option changes the boundary around
the agent and can effectively defeat containment. Examples include
--privileged, --cap-add=SYS_ADMIN, --network=host, mounting / or a
sensitive host directory into the container, mounting
/var/run/docker.sock, replacing the hardened seccomp/AppArmor policy, or
making sensitive host files writable. Review every extra option with the
same care as a full docker run command. --docker is a convenience, not
a safety filter or an approval mechanism.
The documented docker run commands deliberately show every containment flag,
but retyping them is tedious. Use shell functions rather than aliases: a
function evaluates $PWD when it is invoked, forwards arguments to the agent,
and keeps the image-specific flags in one auditable place.
Create ~/.config/agent-containers/shortcuts.sh with the functions below,
then source it from ~/.bashrc or ~/.zshrc:
Restart the shell or run the same source command once to activate them.
Each function mounts the current directory at
/workspace/<current-directory-name> and starts the matching local image.
Arguments normally pass through to the coding agent. To add options to
docker run instead, put them between --docker and --; arguments after
the closing -- still pass through to the agent.
agent_egress_args() {
local agent="$1" fallback="$2" allowlist
allowlist="$HOME/.config/agent-containers/$agent-egress-allowlist.txt"
if [ -f "$allowlist" ]; then
AGENT_EGRESS_ARGS=(-v "$allowlist:/etc/agent/egress-allowlist.txt:ro")
else
AGENT_EGRESS_ARGS=(-e "AGENT_ALLOWED_EGRESS=$fallback")
fi
}
agent_split_args() {
AGENT_DOCKER_ARGS=()
AGENT_CLI_ARGS=()
if [ "${1-}" = "--docker" ]; then
shift
while [ "$#" -gt 0 ] && [ "$1" != "--" ]; do
AGENT_DOCKER_ARGS+=("$1")
shift
done
if [ "${1-}" = "--" ]; then
shift
fi
fi
AGENT_CLI_ARGS=("$@")
}
contained_adal() {
agent_split_args "$@"
agent_egress_args adal "${CONTAINED_ADAL_EGRESS:-adal.sylph.ai}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
"${AGENT_EGRESS_ARGS[@]}" \
-v "adal-home${CONTAINED_ADAL_TAG:+-$CONTAINED_ADAL_TAG}":/home/adal \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
"${AGENT_DOCKER_ARGS[@]}" \
"adal:${CONTAINED_ADAL_TAG:-latest}" adal "${AGENT_CLI_ARGS[@]}"
}
contained_aider() {
agent_split_args "$@"
if [ ! -f "$HOME/.config/agent-containers/aider-egress-allowlist.txt" ]; then
: "${CONTAINED_AIDER_EGRESS:?Set CONTAINED_AIDER_EGRESS to the selected provider hosts.}"
fi
agent_egress_args aider "${CONTAINED_AIDER_EGRESS:-}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
"${AGENT_EGRESS_ARGS[@]}" \
-v "aider-home${CONTAINED_AIDER_TAG:+-$CONTAINED_AIDER_TAG}":/home/aider \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
"${AGENT_DOCKER_ARGS[@]}" \
"aider:${CONTAINED_AIDER_TAG:-latest}" aider "${AGENT_CLI_ARGS[@]}"
}
contained_claude() {
agent_split_args "$@"
agent_egress_args claude "${CONTAINED_CLAUDE_EGRESS:-api.anthropic.com}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
"${AGENT_EGRESS_ARGS[@]}" \
-v "claude-home${CONTAINED_CLAUDE_TAG:+-$CONTAINED_CLAUDE_TAG}":/home/claude \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
"${AGENT_DOCKER_ARGS[@]}" \
"claude-code:${CONTAINED_CLAUDE_TAG:-latest}" claude "${AGENT_CLI_ARGS[@]}"
}
contained_codex() {
agent_split_args "$@"
agent_egress_args codex "${CONTAINED_CODEX_EGRESS:-api.openai.com,auth.openai.com,chatgpt.com}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
-e OPENAI_API_KEY \
"${AGENT_EGRESS_ARGS[@]}" \
-v "codex-home${CONTAINED_CODEX_TAG:+-$CONTAINED_CODEX_TAG}":/home/codex \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
"${AGENT_DOCKER_ARGS[@]}" \
"codex:${CONTAINED_CODEX_TAG:-latest}" codex "${AGENT_CLI_ARGS[@]}"
}
contained_kilo() {
agent_split_args "$@"
agent_egress_args kilo "${CONTAINED_KILO_EGRESS:-api.kilo.ai}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp:exec --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
"${AGENT_EGRESS_ARGS[@]}" \
-v "kilo-home${CONTAINED_KILO_TAG:+-$CONTAINED_KILO_TAG}":/home/kilo \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
"${AGENT_DOCKER_ARGS[@]}" \
"kilo-code:${CONTAINED_KILO_TAG:-latest}" kilo "${AGENT_CLI_ARGS[@]}"
}
contained_opencode() {
agent_split_args "$@"
agent_egress_args opencode "${CONTAINED_OPENCODE_EGRESS:-opencode.ai,models.dev}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp:exec --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
"${AGENT_EGRESS_ARGS[@]}" \
-v "opencode-home${CONTAINED_OPENCODE_TAG:+-$CONTAINED_OPENCODE_TAG}":/home/opencode \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
"${AGENT_DOCKER_ARGS[@]}" \
"opencode:${CONTAINED_OPENCODE_TAG:-latest}" opencode "${AGENT_CLI_ARGS[@]}"
}
contained_qwen() {
agent_split_args "$@"
agent_egress_args qwen "${CONTAINED_QWEN_EGRESS:-dashscope.aliyuncs.com}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
-e OPENAI_API_KEY \
-e "OPENAI_BASE_URL=${CONTAINED_QWEN_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}" \
-e "OPENAI_MODEL=${CONTAINED_QWEN_MODEL:-qwen3-coder-plus}" \
"${AGENT_EGRESS_ARGS[@]}" \
-v "qwen-home${CONTAINED_QWEN_TAG:+-$CONTAINED_QWEN_TAG}":/home/qwen \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
"${AGENT_DOCKER_ARGS[@]}" \
"qwen-code:${CONTAINED_QWEN_TAG:-latest}" qwen "${AGENT_CLI_ARGS[@]}"
}
contained_hermes() {
agent_split_args "$@"
if [ ! -f "$HOME/.config/agent-containers/hermes-egress-allowlist.txt" ]; then
: "${CONTAINED_HERMES_EGRESS:?Set CONTAINED_HERMES_EGRESS to the selected provider hosts.}"
fi
agent_egress_args hermes "${CONTAINED_HERMES_EGRESS:-}"
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp --tmpfs /run:exec \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID --cap-add=CHOWN --cap-add=DAC_OVERRIDE \
"${AGENT_EGRESS_ARGS[@]}" \
-v hermes-data:/opt/data \
"${AGENT_DOCKER_ARGS[@]}" \
"hermes:${CONTAINED_HERMES_TAG:-latest}" "${AGENT_CLI_ARGS[@]}"
}
For example, after building the images:
contained_codex
contained_adal
contained_opencode
CONTAINED_AIDER_EGRESS='api.anthropic.com' contained_aider --docker -e ANTHROPIC_API_KEY --
CONTAINED_HERMES_EGRESS='openrouter.ai' contained_hermes
To add a genuinely input-only bind mount for one run, pass the Docker options before the separator:
The same form works with every shortcut. Agent arguments belong after the separator:
contained_claude --docker \
-v "$PWD/claude-settings.json:/home/claude/.claude/settings.json:ro" \
-- --version
Without --docker, all arguments continue to pass directly to the agent, as
before. Docker options must precede the image name, so the opening marker is
required even when an option such as -v is unambiguous to a person.
Do not infer that an agent's primary configuration file is safe to mount
read-only or as a single read-write file. See custom configuration
files and the candidate local-model
recipes for the agent-specific constraints.
For Codex device authentication, forward the login subcommand through the
same function. Do not add another codex: the function already supplies the
CLI command required after the image name.
Each function defaults to the :latest tag (e.g. adal:latest), matching a
plain docker build -t adal agent-images/adal with no --build-arg
UID/GID override — the common single-user-per-host case needs no further
configuration. On a host shared by multiple accounts where each user built
their own UID-matched tag (see Matching your host user's
UID/GID),
set the matching CONTAINED_<IMAGE>_TAG variable (e.g.
CONTAINED_ADAL_TAG=alice) in that user's own shell profile before sourcing
shortcuts.sh. For the seven workload images built from the shared
UID/GID template (every function above except contained_hermes), setting
that variable changes two things together: the function runs the matching
adal:alice instead of whatever happens to be tagged adal:latest, and
it mounts a per-user home volume (adal-home-alice instead of the shared
adal-home) — each affected function suffixes its home volume's name with
the same tag, so two UID-tagged images on the same host never collide over
one account's persistent state. hermes has no build-time UID/GID and
always keeps the single shared hermes-data volume regardless of
CONTAINED_HERMES_TAG.
The functions deliberately do not set unrestricted egress. Override a
specific image's CONTAINED_<IMAGE>_EGRESS variable only with the hosts its
selected provider, MCP server, source-control integration, or package
registry requires. Quote a wildcard value, for example
CONTAINED_HERMES_EGRESS='*', when unrestricted egress is intentional.
If $HOME/.config/agent-containers/<agent>-egress-allowlist.txt exists, the
matching function instead mounts it read-only at
/etc/agent/egress-allowlist.txt; the source may be a symlink to a regular
file. The mounted file takes precedence, so the function does not pass
AGENT_ALLOWED_EGRESS in that case.
Refer to each image page for the provider-specific allowlist and authentication
requirements.