AutoAgent 自定义 Docker 沙箱指南:从镜像构建到安全执行环境配置
【免费下载链接】AutoAgent"AutoAgent: Fully-Automated and Zero-Code LLM Agent Framework"项目地址: https://gitcode.com/GitHub_Trending/au/AutoAgent
本指南以官方文档《Comment Créer un Soutien Docker sur Mesure》(自定义 Docker 沙箱指南)为骨架,结合 AutoAgent 仓库源码与运行时文档展开。默认的沙箱环境只预装精简的基础软件,当代理任务需要 Node.js、Ruby 等额外运行时或特定系统依赖时,你需要构建自己的 Docker 镜像并接入框架。读完本文,你将掌握:编写基于 Debian/Ubuntu 的自定义镜像、在
config.toml中指定沙箱镜像、理解镜像首次构建与复用的底层机制,以及解决 UID 冲突与端口占用两类常见故障。
为什么需要自定义沙箱
沙箱(Sandbox)是代理执行任务的地方。代理并不会直接在你的宿主机上运行命令(这存在安全风险),而是在 Docker 容器内运行。默认的 OpenHands 沙箱镜像(python-nodejs:python3.12-nodejs22,源自 nikolaik/python-nodejs)预装了 Python、Node.js 等常用软件,但你的用例可能还需要默认镜像中没有的软件——例如 Ruby、特定版本的编译器、系统级库等。
从仓库的运行时文档 docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/architecture/runtime.md 可以看到,沙盒运行时存在的核心价值包括:
- 安全性:不受信任的代码在隔离环境中执行,无法访问或修改宿主系统资源;
- 一致性:代码执行在不同机器与配置下保持一致,消除"在我机器上能跑"的问题;
- 资源控制:防止失控进程影响宿主机;
- 可重现性:执行环境一致且可控,便于复现错误。
因此,为沙箱预装任务所需的全部依赖,是保证代理稳定高效执行的前提。你可以通过两种方式自定义沙箱:
- 直接使用一个已包含所需软件的现成镜像(可跳过"创建你的 Docker 镜像"一节);
- 基于 Debian/Ubuntu 构建你自己的自定义镜像。
创建你的自定义 Docker 镜像
编写 Dockerfile
要创建自定义镜像,它必须基于 Debian(官方文档明确要求基于 debian/ubuntu)。例如,若希望沙箱内具备node二进制,可以编写如下 Dockerfile:
# 以最新的 ubuntu 为基础镜像 FROM ubuntu:latest # 执行必要的更新 RUN apt-get update && apt-get install -y nodejs以 Ruby 为例,同样可以这样写:
FROM debian:latest # 安装所需软件包 RUN apt-get update && apt-get install -y ruby注意:在本文描述的配置下,OpenHands 将以用户
openhands的身份在沙箱内运行,因此通过 Dockerfile 安装的所有软件包应对系统上的所有用户可用,而不仅是 root。上面的apt-get install安装的 nodejs/ruby 对全系统所有用户生效,符合这一要求。
构建镜像
将上述内容保存为名为Dockerfile的文件,放在一个独立目录中。然后在终端中导航到该目录并执行:
docker build -t custom-image .该命令会生成一个名为custom-image的新镜像,并保存在本地 Docker Engine 中,可供后续引用。
在 config.toml 中指定自定义镜像
OpenHands 的配置通过顶层文件config.toml完成。在 OpenHands 目录下创建(或编辑)config.toml,写入以下内容:
[core] workspace_base="./workspace" run_as_openhands=true sandbox_base_container_image="custom-image"其中各字段含义如下:
| 配置项 | 说明 |
|---|---|
workspace_base | 工作区的基础路径(默认"./workspace"),沙箱中挂载的宿主目录 |
run_as_openhands | 是否以openhands用户身份运行,布尔值,默认true |
sandbox_base_container_image | 指定沙箱使用的基础镜像名称,必须设置为上一步构建的自定义镜像名 |
这里可以直接使用你本地构建的镜像名,也可以使用已经拉取到本地的任意镜像。从配置文档 docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/configuration-options.md 中可以看到,run_as_openhands的默认值即为true,即框架默认在沙箱内以非 root 用户执行任务。
运行并验证
在项目根目录执行:
make run启动后,在浏览器中访问localhost:3001,即可在界面中检查所需依赖是否可用。以 nodejs 示例为例,在控制台执行node -v,输出应为类似v18.19.1的版本号。若看到预期版本号,说明自定义镜像已成功接入沙箱环境。
技术原理解析:镜像的首次构建与复用
自定义镜像并非被直接使用,OpenHands 会基于它派生出一个"运行时镜像"。整个流程可以概括为:
- 首次使用:当某个自定义镜像第一次被使用且未找到对应的运行时镜像时,框架会基于它执行一次构建;
- 后续复用:构建完成后,运行时镜像会被缓存并打上特殊标签,之后的运行会直接命中缓存并返回,无需重复构建。
构建由_build_sandbox_image()完成:它把你的自定义镜像作为基础(FROM {base_image}),随后注入 OpenHands 运行所需的系统组件与 conda 环境,生成的 Dockerfile 内容示意如下:
dockerfile_content = ( f'FROM {base_image}\n' 'RUN apt update && apt install -y openssh-server wget sudo\n' 'RUN mkdir -p -m0755 /var/run/sshd\n' 'RUN mkdir -p /openhands && mkdir -p /openhands/logs && chmod 777 /openhands/logs\n' 'RUN wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"\n' 'RUN bash Miniforge3-$(uname)-$(uname -m).sh -b -p /openhands/miniforge3\n' 'RUN bash -c ". /openhands/miniforge3/etc/profile.d/conda.sh && conda config --set changeps1 False && conda config --append channels conda-forge"\n' 'RUN echo "export PATH=/openhands/miniforge3/bin:$PATH" >> ~/.bashrc\n' 'RUN echo "export PATH=/openhands/miniforge3/bin:$PATH" >> /openhands/bash.bashrc\n' ).strip()可以看到,整个构建包含三个层次:
- 系统层:安装
openssh-server、wget、sudo,并初始化 sshd 运行目录; - conda 层:下载并安装 Miniforge3 到
/openhands/miniforge3,配置 conda-forge 频道; - 环境层:将 miniforge 的 bin 目录写入
~/.bashrc与/openhands/bash.bashrc,确保登录 shell 能直接使用 conda。
此外,镜像名称会通过_get_new_image_name()被改写,后续运行查找的正是这个被改写后的名称——这正是"首次构建、后续命中缓存"机制的实现基础。
三标签缓存体系
从运行时文档 runtime.md 可知,OpenHands 为运行时镜像维护了一套三标签缓存体系,用于平衡可重现性与构建速度:
- 版本标签:
oh_v{openhands_version}_{base_image},最通用; - 锁定标签:
oh_v{openhands_version}_{16_digit_lock_hash},哈希由基础镜像名、pyproject.toml与poetry.lock内容计算,独立于源代码; - 源码标签:
oh_v{openhands_version}_{lock_hash}_{source_hash},最具体,哈希来自源码目录。
构建时按"源码标签 → 锁定标签 → 版本标签 → 从零构建"的优先级查找可复用镜像,从而在代码微小改动时快速重建、在完全一致时跳过构建,大幅缩短启动时间。
AutoAgent 源码视角:沙箱环境与基础镜像管理
虽然官方自定义沙箱指南以 OpenHands 的config.toml为配置入口,但在 AutoAgent 仓库中,沙箱运行时的底层实现同样值得关注,它可以帮助你理解代理与 Docker 容器的实际交互方式。
DockerEnv 与 DockerConfig
AutoAgent 的沙箱执行环境由 autoagent/environment/docker_env.py 中的DockerEnv类实现,其配置由DockerConfig数据类承载:
@dataclass class DockerConfig: container_name: str # Docker 容器名称 workplace_name: str # 工作区目录名(容器内挂载点) communication_port: int # 通信端口,默认 12345 conda_path: str # 容器内 conda 路径,如 /root/miniconda3 test_pull_name: str = field(default='main') # 测试分支名 task_name: Optional[str] = field(default=None) git_clone: bool = field(default=False) # 是否在容器内克隆 AutoAgent 仓库 setup_package: Optional[str] = field(default=None) local_root: str = field(default=os.getcwd()) # 本地工作区根目录从源码结构看,DockerEnv.init_container()完成容器生命周期的管理:先检查同名容器是否已存在(已运行则跳过、已停止则启动),否则执行docker run创建新容器,并将tcp_server.py复制到工作区、以--user root启动,同时把本地工作区目录-v挂载进容器、映射通信端口-p:
docker_command = [ "docker", "run", "-d", "--name", self.container_name, "--user", "root", "-v", f"{self.local_workplace}:{self.docker_workplace}", "-w", f"{self.docker_workplace}", "-p", f"{self.communication_port}:{self.communication_port}", BASE_IMAGES, "/bin/bash", "-c", f"python3 {self.docker_workplace}/tcp_server.py --workplace {self.workplace_name} --conda_path {self.conda_path} --port {self.communication_port}" ]容器启动后,DockerEnv.run_command()通过 socket 连接容器内的tcp_server.py,以 JSON 协议逐行收发命令与流式输出——这是代理在沙箱中执行命令的通信通道。同时with_env()装饰器将环境实例注入工具函数,使各工具无需感知底层是 Docker 还是本地环境。
基础镜像的自动选择
AutoAgent 的基础镜像并非写死,而是由 constant.py 统一管理:
BASE_IMAGES = os.getenv('BASE_IMAGES', None) def get_architecture(): machine = platform.machine().lower() if 'x86' in machine or 'amd64' in machine or 'i386' in machine: return "tjbtech1/metachain:amd64_latest" elif 'arm' in machine: return "tjbtech1/metachain:latest" else: return "tjbtech1/metachain:latest" if BASE_IMAGES is None: BASE_IMAGES = get_architecture()也就是说:你可以通过环境变量BASE_IMAGES显式指定镜像;若未设置,框架会根据当前机器的 CPU 架构(x86/amd64 或 arm)自动选择预构建镜像。README(README.md)中也提到:"You don't need to manually pull the pre-built image, because we have let Auto-Deep-Research automatically pull the pre-built image based on your architecture of your machine." 即无需手动拉取镜像,框架会按架构自动完成。这与官方自定义沙箱指南中"手动指定sandbox_base_container_image"的思路互补:前者面向一键开箱,后者面向深度定制。
如果希望像官方指南那样完全掌控沙箱内容,可以将BASE_IMAGES设为你自定义构建的镜像,例如:
BASE_IMAGES=custom-image auto main故障排除
错误:useradd: UID 1000 is not unique
现象:控制台输出中出现useradd: UID 1000 is not unique。
原因:OpenHands 尝试在沙箱内以 UID 1000 创建openhands用户,但该 UID 在自定义镜像中已被占用(例如某些镜像预置了 uid 1000 的用户)。
解决:在config.toml中修改sandbox_user_id为其他值:
[core] workspace_base="./workspace" run_as_openhands=true sandbox_base_container_image="custom-image" sandbox_user_id="1001"端口被占用错误
现象:提示端口被占用或不可用。
解决:删除所有正在运行的 Docker 容器后重新执行。先通过docker ps查看当前容器,再对相关容器执行docker rm <container>(必要时先docker stop <container>),最后重新运行make run。
结合 AutoAgent 源码,在 autoagent/environment/docker_env.py 中容器端口信息由
check_container_ports()解析docker ps输出的端口映射(如0.0.0.0:12345->12345/tcp),并在wait_for_container_ready()中确认端口与tcp_server.py进程均就绪后才认为容器启动成功。因此端口冲突会直接导致沙箱初始化失败,清理容器后重试是最直接的恢复手段。
小结
自定义 Docker 沙箱是让代理按需获得运行环境的标准化手段。整个流程可以概括为四步:
- 编写 Dockerfile:基于 Debian/Ubuntu,用
apt-get安装任务所需软件(注意软件需对所有用户可用); - 构建镜像:
docker build -t <镜像名> .; - 接入框架:在
config.toml的[core]段设置sandbox_base_container_image(AutoAgent 亦可直接通过环境变量BASE_IMAGES指定); - 运行验证:
make run后在localhost:3001用node -v等命令确认依赖就绪。
底层机制上,OpenHands 会将自定义镜像二次构建为运行时镜像,并通过三标签体系缓存复用;AutoAgent 侧则由DockerEnv/DockerConfig管理容器生命周期、以 TCP 通道执行命令。遇到 UID 冲突修改sandbox_user_id,遇到端口占用则清理 Docker 容器后重试。掌握了这些,你就能为任意代理任务定制安全、可重现的执行环境。
【免费下载链接】AutoAgent"AutoAgent: Fully-Automated and Zero-Code LLM Agent Framework"项目地址: https://gitcode.com/GitHub_Trending/au/AutoAgent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考