nothingnesses/agent-images
GitHub: nothingnesses/agent-images
基于 Nix 可复现构建的沙箱化 OCI 容器镜像集合,用于安全隔离各类 AI 编程 agent,防止其越权访问宿主机敏感文件。
Stars: 11 | Forks: 2
# agent-images
使用 Nix 可复现构建的,专为 AI 编程 agent 设计的沙箱化 OCI 容器镜像。
消费来自 [llm-agents.nix](https://github.com/numtide/llm-agents.nix) 的 agent 包,并生成可通过 [agent-box](https://github.com/0xferrous/agent-box) 或独立 Podman/Docker 使用的镜像。
```
llm-agents.nix (packages) -> agent-images (images) -> agent-box (orchestration)
```
## 为什么
AI 编程 agent 需要访问你的文件系统才能发挥作用,但这意味它们也可以读取诸如 SSH 密钥、云凭证和 API token 之类的秘密信息。
在容器中运行 agent 可以限制它们的可见范围。Nix 使镜像具备可复现性且易于定制。
## 可用镜像
#### AI 编程 Agent
| 镜像 | Agent | 构建 |
| ---------------- | ----------------------------------------------------------------- | ---------------------------- |
| `amp` | [Amp](https://ampcode.com/) | `nix build .#amp` |
| `claude-code` | [Claude Code](https://claude.ai/code) | `nix build .#claude-code` |
| `cli-proxy-api` | [CLI Proxy API](https://github.com/router-for-me/CLIProxyAPI) | `nix build .#cli-proxy-api` |
| `code` | [Code](https://github.com/just-every/code/) | `nix build .#code` |
| `codex` | [Codex CLI](https://github.com/openai/codex) | `nix build .#codex` |
| `copilot-cli` | [Copilot CLI](https://github.com/github/copilot-cli) | `nix build .#copilot-cli` |
| `crush` | [Crush](https://github.com/charmbracelet/crush) | `nix build .#crush` |
| `cursor-agent` | [Cursor Agent](https://cursor.com/) | `nix build .#cursor-agent` |
| `droid` | [Droid](https://factory.ai) | `nix build .#droid` |
| `eca` | [ECA](https://github.com/editor-code-assistant/eca) | `nix build .#eca` |
| `forge` | [Forge](https://github.com/antinomyhq/forge) | `nix build .#forge` |
| `gemini-cli` | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `nix build .#gemini-cli` |
| `goose-cli` | [Goose](https://github.com/block/goose) | `nix build .#goose-cli` |
| `iflow-cli` | [iFlow CLI](https://github.com/iflow-ai/iflow-cli) | `nix build .#iflow-cli` |
| `jules` | [Jules](https://jules.google) | `nix build .#jules` |
| `kilocode-cli` | [Kilocode CLI](https://kilocode.ai/cli) | `nix build .#kilocode-cli` |
| `letta-code` | [Letta Code](https://github.com/letta-ai/letta-code) | `nix build .#letta-code` |
| `mistral-vibe` | [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | `nix build .#mistral-vibe` |
| `nanocoder` | [Nanocoder](https://github.com/Mote-Software/nanocoder) | `nix build .#nanocoder` |
| `oh-my-opencode` | [Oh My OpenCode](https://github.com/code-yeongyu/oh-my-openagent) | `nix build .#oh-my-opencode` |
| `omp` | [OMP](https://github.com/can1357/oh-my-pi) | `nix build .#omp` |
| `opencode` | [OpenCode](https://github.com/anomalyco/opencode) | `nix build .#opencode` |
| `pi` | [Pi](https://github.com/badlogic/pi-mono) | `nix build .#pi` |
| `qoder-cli` | [Qoder CLI](https://qoder.com) | `nix build .#qoder-cli` |
| `qwen-code` | [Qwen Code](https://github.com/QwenLM/qwen-code) | `nix build .#qwen-code` |
#### AI 助手
| 镜像 | Agent | 构建 |
| -------------- | ------------------------------------------------------ | -------------------------- |
| `hermes-agent` | [Hermes Agent](https://hermes-agent.nousresearch.com/) | `nix build .#hermes-agent` |
| `localgpt` | [LocalGPT](https://github.com/localgpt-app/localgpt) | `nix build .#localgpt` |
| `openclaw` | [OpenClaw](https://openclaw.ai) | `nix build .#openclaw` |
| `picoclaw` | [PicoClaw](https://picoclaw.io) | `nix build .#picoclaw` |
| `zeroclaw` | [ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw) | `nix build .#zeroclaw` |
每个镜像都包含一组默认的基础包:git, coreutils, bash, ripgrep, findutils, grep, sed, gawk, diff, jq, tar, gzip, less, curl, which 和 CA 证书。可以通过 `basePackages` 参数覆盖它们(参见[自定义镜像](#custom-images))。默认情况下,容器以非 root 的 `agent` 用户 (uid 1000) 运行,并将 `/workspace` 作为工作目录。镜像还会在 `$HOME` 下预先创建标准的 XDG 基础目录 (`.config`, `.cache`, `.local/share`, `.local/state`),这样将子路径挂载到这些目录时就不会留下属于 root 用户的父目录。用户和工作目录都可以自定义(参见[自定义镜像](#custom-images))。
## 系统要求
- 启用了 [flakes 支持](https://wiki.nixos.org/wiki/Flakes) 的 [Nix](https://nixos.org/)
- 用于加载和运行镜像的 [Podman](https://podman.io/) 或 [Docker](https://www.docker.com/)
所有其他依赖项(包括来自 [llm-agents.nix](https://github.com/numtide/llm-agents.nix) 的 agent 包)都将由 Nix flake 自动解析。NixOS 用户还应遵循下面的 [rootless Podman 设置](#nixos-rootless-podman-setup) 步骤。
**macOS:** 镜像仅支持 Linux。在 macOS 上,需要明确指定目标系统,并确保你配置了 [Linux 远程构建器](https://nix.dev/manual/nix/latest/advanced-topics/distributed-builds)(例如通过 Docker Desktop 或 nix-darwin 的 `linux-builder`):
```
nix build .#packages.x86_64-linux.
# 或对于 ARM:
nix build .#packages.aarch64-linux.
```
开发工具(`nix fmt`, `nix develop`, `nix flake check`)可以在 macOS 上原生运行。
## 快速入门
```
# 列出所有可用镜像及其描述
nix search . ^
# 将 替换为上表中的任意镜像名称
nix build .#
podman load < result # or: docker load < result
podman run --rm localhost/agent-images/:latest --version
```
### 独立使用
为你选择的提供商传入 API key:
```
# Claude Code (Anthropic)
podman run --rm -it \
-v ./my-project:/workspace \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
localhost/agent-images/claude-code:latest
# OpenCode (OpenRouter)
podman run --rm -it \
-v ./my-project:/workspace \
-e OPENROUTER_API_KEY="$OPENROUTER_API_KEY" \
localhost/agent-images/opencode:latest
```
### 验证容器内部结构
```
# 将 替换为上面使用的镜像名称
podman run --rm --entrypoint sh localhost/agent-images/:latest \
-c 'whoami && echo $HOME && pwd && command -v git && command -v rg'
```
预期输出:
```
agent
/home/agent
/workspace
/nix/store/.../bin/git
/nix/store/.../bin/rg
```
## 配合 agent-box 使用
### 全局配置
创建 `~/.agent-box.toml`:
```
workspace_dir = "~/.local/agent-box/workspaces"
base_repo_dir = "~/path/to/your/projects"
[runtime]
backend = "podman"
# 将 替换为上表中的任意镜像名称
image = "localhost/agent-images/:latest"
env_passthrough = ["ANTHROPIC_API_KEY", "OPENROUTER_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY"]
```
`base_repo_dir` 必须是包含你 git 仓库的实际(非符号链接的)父目录。Agent-box 会解析符号链接,因此将仓库符号链接到单独的目录将不起作用。将你的 agent 需要的任何其他 API key 添加到 `env_passthrough` 中。
### 本地模式
将当前目录原样挂载到容器中。agent 可以看到所有文件,包括未跟踪和被 gitignore 的文件。
```
cd ~/projects/my-repo
ab spawn --local
```
### Worktree 模式(沙箱化)
创建一个 git worktree,使 agent 只能看到已提交/跟踪的文件。被 gitignore 的文件(如 `result`)将不可见。
```
# 创建一个工作区(在 repo 目录内)
ab new my-repo -s my-session --git
# 启动容器
ab spawn -s my-session --git
```
### 运行一次性命令
使用 `--entrypoint` 覆盖默认的 entrypoint,并使用 `-c` 传递参数:
```
# 检查 agent 版本
ab spawn --local --entrypoint -c="--version"
# 读取文件
ab spawn --local --entrypoint cat -c="README.md"
# 运行 shell 命令(注意:将每个参数作为单独的 -c 传递)
ab spawn --local --entrypoint sh -c="-c" -c="whoami && pwd"
```
### 沙箱验证
要验证 worktree 模式是否会隐藏被 gitignore 的文件:
```
# 首先构建一个镜像(创建一个 `result` 软链接,该链接已被 gitignore)
# 将 替换为上表中的任意镜像名称
nix build .#
# 本地模式 - agent 可以看到 result
ab spawn --local --entrypoint ls -c="-la" -c="result"
# 输出:result -> /nix/store/...
# Worktree 模式 - agent 无法看到 result
ab new my-repo -s sandbox-test --git
ab spawn -s sandbox-test --git --entrypoint ls -c="result"
# 输出:ls: cannot access 'result': No such file or directory
```
## NixOS:Rootless Podman 设置
NixOS 需要额外的配置才能在这些镜像上使用 rootless Podman。请将以下内容添加到你的 `configuration.nix` 中:
```
virtualisation = {
containers.enable = true;
podman = {
enable = true;
dockerCompat = true;
};
};
users.users. = {
extraGroups = [ "podman" ];
subUidRanges = [{ startUid = 100000; count = 65536; }];
subGidRanges = [{ startGid = 100000; count = 65536; }];
};
```
然后重新构建:`sudo nixos-rebuild switch`
**注意:** 下面的 `sudo` 命令以及 `podman load`/`podman system reset` 命令必须在你自己的终端中运行。沙箱化环境(例如在容器内运行的 AI 编程 agent)由于启用了 "no new privileges" 标志,无法执行 `sudo` 或访问 `/etc/subuid`。
你还需要一个容器信任策略。创建 `~/.config/containers/policy.json`:
```
{
"default": [{ "type": "insecureAcceptAnything" }]
}
```
### 故障排除
**加载失败后存储损坏。** 如果 `podman load` 失败(例如因为缺少 `/etc/subuid`),Podman 的存储可能会损坏。修复方法如下:
```
podman system reset --force
podman load < result
```
**`newuidmap: Too many levels of symbolic links`。** 当 `/etc/subuid` 是一个符号链接(例如来自 `environment.etc` 条目)时,就会发生这种情况。NixOS 的 setuid 包装器无法跟随符号链接。请移除 `subuid`/`subgid` 的所有 `environment.etc` 条目,仅依赖 `subUidRanges`/`subGidRanges`,它们会创建真实文件。重新构建并重置 Podman 存储。
## 自定义镜像
使用 `mkAgentImage` 构建你自己的 agent 镜像:
```
{
inputs.agent-images.url = "github:nothingnesses/agent-images";
outputs = { agent-images, nixpkgs, ... }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
mkAgentImage = agent-images.lib.mkAgentImage { inherit pkgs; };
in {
packages.x86_64-linux.my-agent = mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
extraPackages = [ pkgs.nodejs ];
extraEnv = { MY_VAR = "value"; };
extraDirectories = [ "~/.my-agent-cache" "/opt/my-agent-cache" ];
};
};
}
```
### 覆盖基础包
默认情况下,镜像包含一组标准的 CLI 工具(bash, coreutils, git 等)。传入 `basePackages` 可以完全替换它们:
```
mkAgentImage {
name = "my-minimal-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
basePackages = with pkgs; [ bashInteractive coreutils git cacert ];
}
```
### 自定义用户和工作目录
默认情况下,容器以 `agent` 用户 (uid/gid 1000) 运行,并将 `/workspace` 作为工作目录。使用 `user`, `uid`, `gid` 和 `workingDir` 可以覆盖这些设置:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
user = "dev";
uid = 1001;
gid = 100; # defaults to uid if omitted
workingDir = "/project";
}
```
独立于 `uid` 设置 `gid` 对于 host 组(例如 `users`, gid 100)与其 uid 不同的 rootless Podman 用户非常有用。如果不这样做,在容器内创建的文件的 gid 可能会映射到 host 上的意外值。
### XDG 基础目录和额外可写路径
`mkAgentImage` 始终会创建由运行时用户拥有的 `$HOME`、工作目录以及以下 XDG 基础目录:
- `$HOME/.config`
- `$HOME/.cache`
- `$HOME/.local/share`
- `$HOME/.local/state`
相应的环境变量(`XDG_CONFIG_HOME`, `XDG_CACHE_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`)也会被设置为这些默认值。如果你通过 `extraEnv` 覆盖了其中任何一个,该变量的默认设置将被抑制以避免重复。
这可以避免一个常见的容器运行时陷阱,即挂载 `/home/agent/.config/git` 等子目录会导致缺失的父目录被自动创建为 `root:root`。
如果你需要更多由运行时用户拥有的可写目录,请将 `extraDirectories` 作为绝对容器路径或相对于容器用户主目录的 `~/...` 路径传入:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
extraDirectories = [
"~/.my-agent-cache"
"/opt/my-agent/state"
];
}
```
只有列出的路径会被 chown 给运行时用户。由 `mkdir -p` 为 `$HOME` 外部的路径创建的中间父目录仍归 root 所有。如果你需要可写的中间目录,请在 `extraDirectories` 中明确列出它们。
要使用非标准的 XDG 路径,请结合使用 `extraDirectories`(用于创建目录)和 `extraEnv`(用于设置环境变量):
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
extraDirectories = [ "~/.custom-config" ];
extraEnv = { XDG_CONFIG_HOME = "/home/agent/.custom-config"; };
}
```
`XDG_RUNTIME_DIR` 被有意排除在外。它由 `pam_systemd` 管理,需要具有严格生命周期语义的 tmpfs,并且无法在容器镜像中有意义地预先创建。
`extraDirectories` 条目会在构建时进行验证。路径必须是绝对路径(或使用 `~/`),只能包含字母数字字符、`/`、`_`、`.`、`+`、`@` 和 `-`,并且不得包含 `..` 组件。系统路径(`/etc`, `/bin`, `/usr`, `/lib`, `/sbin`, `/dev`, `/proc`, `/sys`, `/run`, `/tmp`, `/nix`, `/var`, `/root`)将被拒绝,以防止意外更改关键目录的所有权。
## 在容器内使用 Nix
默认情况下,镜像中不包含 Nix CLI。设置 `withNix = true` 可以在容器内启用 Nix 工作流:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
withNix = true;
};
```
这会配置单用户 Nix,并启用 `nix-command` 和 `flakes` 实验性特性。在容器内部,你可以运行:
```
nix --version
nix develop
nix build
nix shell nixpkgs#hello -c hello
nix-shell -p ripgrep --command "rg --version"
```
### 覆盖 Nix 版本
Nix CLI 版本默认为该 flake 的 nixpkgs 锁定的版本。传入 `nixPackage` 可使用不同的版本:
### 自定义实验性特性
默认的实验性特性是 `nix-command` 和 `flakes`。使用 `nixExperimentalFeatures` 进行覆盖:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
withNix = true;
nixExperimentalFeatures = [ "nix-command" "flakes" "pipe-operators" ];
};
```
### 启用 `nix-ld`
[`nix-ld`](https://github.com/nix-community/nix-ld) 有助于运行外部的动态链接二进制文件,这些文件期望使用常规的系统加载器路径,例如 `/lib64/ld-linux-x86-64.so.2`。
使用 `withNixLd = true` 启用它:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
withNixLd = true;
};
```
这会添加 `pkgs.nix-ld`,在镜像内部创建与架构对应的动态链接器符号链接,并自动设置 `NIX_LD`/`NIX_LD_LIBRARY_PATH`。
默认情况下,`NIX_LD` 指向系统动态链接器,`NIX_LD_LIBRARY_PATH` 包含一组镜像自上游 NixOS `programs.nix-ld` 模块的默认常用库(包括 glibc, openssl, zlib, curl, systemd 等)。大多数外部二进制文件无需任何额外配置即可开箱即用。
默认集合镜像自上游 NixOS,其中包含 `systemd` 及其传递依赖。这确保了广泛的兼容性,但也增加了显著的闭包大小。
如果你的外部二进制文件需要默认集合之外的额外共享库,请使用 `extraNixLdLibraries`:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
withNixLd = true;
extraNixLdLibraries = with pkgs; [ SDL2 libGL ];
};
```
要替换整个默认库集合(例如为了最小化镜像大小),请传入 `nixLdLibraries`。这会**替换**而不是扩展默认值,因此你的外部二进制文件必须能够在你提供的列表中找到它们所有的依赖项:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
withNixLd = true;
nixLdLibraries = with pkgs; [ zlib openssl ];
};
```
`withNixLd` 与 `withNix` 相互独立;你可以根据需要启用其中一个、另一个或两者,这取决于你是需要 Nix CLI、`nix-ld` 还是两者都需要。
### 添加 direnv
默认不包含 direnv,但可以通过 `extraPackages` 添加:
```
mkAgentImage {
name = "my-agent";
agent = my-agent-package;
entrypoint = [ "my-agent" ];
withNix = true;
extraPackages = [ pkgs.direnv pkgs.nix-direnv ];
};
```
你还需要连接 shell 钩子。添加一个 `extraEnv` 条目,或者在容器主目录中配置 `.bashrc` 以运行 `eval "$(direnv hook bash)"`。
### 已知限制
- **无构建沙箱:** 容器内的 Nix 构建以 `sandbox = false` 运行,因为容器运行时通常限制命名空间的创建。构建不是隔离的——在容器中成功的派生在沙箱环境中可能会失败。如果你的容器以提升的权限运行,你可以通过挂载自定义的 `nix.conf` 并设置 `sandbox = relaxed` 或 `sandbox = true` 来覆盖此设置。
- **镜像大小:** 启用 `withNix` 和/或 `withNixLd` 会向镜像添加额外的运行时组件。`withNix` 大约会增加 80 MB,而带有默认库集合的 `withNixLd` 大约会增加 40 MB(两者都会随 nixpkgs 锁定版本而变化)。包含较少包的自定义 `nixLdLibraries` 会更小。
- **Rootless Podman UID 重映射:** Rootless Podman 默认重映射 UID,这可能导致在写入容器内的 `/nix/store`, `/tmp` 或 `$HOME` 时出现权限错误。如果遇到这些错误,请传入 `--userns=keep-id` 以将你的 host UID 直接映射到容器中。Docker 和 rootful Podman 没有此问题。
podman run --rm -it \
--userns=keep-id \
-v ./my-project:/workspace \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
localhost/agent-images/claude-code:latest
### Host Store 挂载优化
如果 host 机器安装了 Nix,你可以只读绑定挂载 host store 以避免重复存储路径:
```
podman run --rm -it \
--mount type=bind,src=/nix/store,dst=/nix/store,ro \
-v ./my-project:/workspace \
localhost/agent-images/my-agent:latest
```
这对于减少磁盘使用很有用,但会将容器与 host 的 Nix 安装耦合在一起。
## 开发
所有开发命令都可以通过 [just](https://github.com/casey/just) 使用。使用 `nix develop` 进入开发 shell,以将 `just` 和所有其他工具添加到你的 PATH 中。
### 测试
测试仅限 Linux(它们会构建并运行容器镜像)。在 macOS 上,请明确指定系统,例如 `nix run .#apps.x86_64-linux.test`。
```
just test default # default image (opencode)
AGENT=codex just test default # or specify any agent
just test nix # basic Nix checks (offline)
just test nix-install # runtime install + nix develop (requires network)
just test nix-custom # custom user/uid/gid, experimental features, extraEnv, nix-ld (with Nix)
just test nix-ld # nix-ld without Nix CLI (standalone nix-ld)
just test nix-ld-minimal # nix-ld with custom minimal library set
just test minimal # minimal basePackages (bash, coreutils, cacert only)
just test custom # custom user/uid/gid/workingDir, extraPackages, extraEnv (without Nix)
just test nix-userns # Nix with --userns=keep-id (Podman only, skipped under Docker)
just test-all # run all of the above
```
### 格式化
```
just fmt # format all files (Nix, shell, YAML, Markdown)
just fmt -- --ci # check without modifying (used in CI)
```
进入开发 shell(`nix develop`)时,预提交钩子会自动设置。它们会在每次提交前对暂存文件运行 `nix fmt`、`deadnix` 和 `actionlint`。Shellcheck 作为预推送钩子对 `.bats`、`.bash` 和 `.sh` 文件运行。
任何批量格式化提交的 SHA 都应添加到 [.git-blame-ignore-revs](.git-blame-ignore-revs) 中,并在本地配置:
```
git config blame.ignoreRevsFile .git-blame-ignore-revs
```
### Lint 检查
```
just lint # run all linters (shellcheck, deadnix, actionlint)
just shellcheck # run shellcheck across all test files
just deadnix # find unused bindings in Nix files
just actionlint # validate GitHub Actions workflow files
```
### 完整验证
按顺序运行格式检查、所有 linter 和所有测试:
```
just verify
```
## 许可证
本项目基于 [Blue Oak Model License 1.0.0](LICENSE) 授权。
标签:agent-box, AI安全, AI编程代理, Chat Copilot, Claude Code, Cutter, DevSecOps, DLL 劫持, Docker, GitHub Copilot, llm-agents.nix, Nix, OCI容器镜像, OpenAI Codex, Podman, SOC Prime, StruQ, Web截图, 上游代理, 代码辅助, 可复现构建, 大语言模型, 安全防御评估, 容器安全, 容器编排, 密钥保护, 开发工具, 沙箱, 端侧AI, 网络安全审计, 请求拦截, 隔离执行