3.8 KiB
Docker Image
Base image contents
The sandcage image is built on debian:bookworm-slim and includes:
- Shell: zsh (default), bash
- VCS: git, openssh-client
- Search: ripgrep, fd-find
- Utilities: jq, curl, sudo, ca-certificates, npm
- Task runner: just (installed from upstream binary)
- Python tooling: uv (installed per-user at
/home/agent/.local/bin)
The image creates an agent user (UID 1000) with a home at /home/agent, zsh as the default shell, and passwordless sudo.
Pre-created directories: /workspace, /home/agent/.claude, /home/agent/.codex, /home/agent/.gemini.
How agents are installed
AI coding agents (Claude Code, Codex, Gemini CLI) are not baked into the image. They are installed on first run via their respective entrypoint scripts. The agent home directories (~/.claude, ~/.codex, ~/.gemini) are persisted across runs through volume mounts to ~/.sandcage/ on the host, so installation only happens once.
Each service has its own entrypoint script installed at /usr/local/bin/sandcage-<service>-entrypoint.
Building images
Build the sandcage image with:
sandcage build
Force rebuild
To bypass the cache and rebuild unconditionally:
sandcage build --force
When --force is used, Docker's --no-cache flag is also passed to ensure layers are rebuilt from scratch.
Service filter
Build only images required for specific services:
sandcage build claude shell
Unknown service names produce an error listing available services.
Cache awareness
Sandcage uses hash-based rebuild detection to avoid unnecessary image builds.
On each build, the SHA-256 hash of the Dockerfile content (bundled or custom) is computed and compared against the stored hash in ~/.sandcage/.build-hashes. If they match, the build is skipped with an "up to date" message.
After a successful build, the new hash is persisted. The hash file uses a simple image:hash line format.
This means:
- Editing the Dockerfile (or switching to/from a custom one) triggers a rebuild automatically.
- The
--forceflag bypasses hash comparison entirely.
Custom Dockerfiles
You can override the bundled Dockerfile by specifying a path in configuration.
Global config (~/.sandcage/config.toml)
[dockerfiles]
sandcage = "/path/to/my/Dockerfile"
Project config (.sandcage.yml)
dockerfiles:
sandcage: ./docker/Dockerfile.custom
The path can point to either:
- A file -- used as the Dockerfile with a temporary build context.
- A directory -- used as the full build context (must contain a
Dockerfile).
trusted_projects requirement
Project-level Dockerfile overrides are only applied if the project directory is listed in the global trusted_projects setting. This prevents untrusted repositories from injecting arbitrary build instructions.
# ~/.sandcage/config.toml
trusted_projects = [
"/home/user/projects/my-trusted-repo",
"/home/user/work",
]
A project is considered trusted if its canonical path starts with any entry in trusted_projects (prefix matching). If a project specifies dockerfiles but is not trusted, the override is silently ignored with a warning.
Global config overrides (~/.sandcage/config.toml) are always trusted.
Cross-platform notes
UID/GID handling
On Linux, sandcage queries the host user's UID and GID via id -u / id -g and passes them into the container as SANDCAGE_UID and SANDCAGE_GID. This ensures files created inside the container have correct ownership on bind-mounted host directories.
On Windows, UID/GID passthrough is not meaningful. Sandcage hardcodes both to 1000, matching the agent user built into the image. File ownership on Windows bind mounts is handled by Docker Desktop's filesystem sharing layer.