系列教程 · 第三篇

Docker-Java 完全指南
AI Agent 的关键基础设施工具

AI Agent 需要一双"手"来操作真实世界——运行代码、启动服务、隔离环境。Docker-Java 就是那双手。它让你的 Java Agent 能通过代码创建容器、执行命令、管理环境,像操控乐高积木一样编排基础设施。

← 第一篇:LangChain4j | 第二篇:LangGraph4j

一、开篇概览

Docker-Java 是最成熟的 Java Docker 客户端库(GitHub 3.2k+ Stars),它把 Docker Engine 的所有 REST API 封装成了流畅的 Java API。通过它,你的 Java 程序可以像在命令行敲 docker 命令一样,完全控制 Docker——拉取镜像、创建容器、执行命令、管理网络和卷。

💡 一句话总结
Docker-Java = Docker CLI 的 Java 版本。你能在终端用 docker 做的事,都能用 Java 代码做。

AI Agent 为什么需要 Docker?

在前两篇教程中,我们学了如何用 LangChain4j 调用 LLM,用 LangGraph4j 编排复杂工作流。但 Agent 的真正价值在于执行动作——而最强大、最安全的执行方式就是在 Docker 容器中运行

AI Agent (LangGraph4j) ──调用──▶ Docker-Java 客户端 ──控制──▶ Docker 容器(沙箱)
Agent 决策 → Docker 执行 → 安全隔离

Agent + Docker 的典型场景

  • 代码沙箱:Agent 生成代码后在容器中安全执行
  • 环境创建:Agent 按需启动数据库、缓存等服务
  • 工具隔离:每个 Tool 运行在独立容器,互不干扰
  • 批量任务:Agent 并行启动多个容器处理数据
  • 自愈系统:Agent 监控容器健康,自动重建故障服务

不用 Docker 的风险

  • Agent 执行的代码直接跑在宿主机,安全隐患大
  • 环境依赖混乱,不同任务互相冲突
  • 没有资源限制,恶意代码可能耗尽系统资源
  • 难以清理,残留进程和文件污染系统
DockerClient 连接 镜像拉取 / 构建 容器创建 / 启动 / 停止 容器内执行命令 (exec) 日志获取与监控 网络与卷管理 文件传输 安全沙箱设计 Agent + Docker 集成

✅ 本节小结

  • Docker-Java 让 Java 程序完全控制 Docker Engine
  • AI Agent + Docker = 安全、隔离、可控的代码执行环境
  • 这是构建生产级 Agent 的关键基础设施能力

二、环境搭建

⚙️ 前置要求
Docker Engine 已安装并运行(docker info 可以正常执行)
JDK 11+(推荐 17+)
MavenGradle

Docker-Java 采用模块化设计。核心包 + 传输层实现可以灵活组合:

Maven 依赖(推荐组合)
pom.xml
<properties>
    <docker-java.version>3.4.1</docker-java.version>
</properties>

<dependencies>
    <!-- 核心 API -->
    <dependency>
        <groupId>com.github.docker-java</groupId>
        <artifactId>docker-java-core</artifactId>
        <version>${docker-java.version}</version>
    </dependency>

    <!-- 传输层:Apache HttpClient 5(推荐) -->
    <dependency>
        <groupId>com.github.docker-java</groupId>
        <artifactId>docker-java-transport-httpclient5</artifactId>
        <version>${docker-java.version}</version>
    </dependency>
</dependencies>

或者用一键全包引入(包含所有传输层):

<dependency>
    <groupId>com.github.docker-java</groupId>
    <artifactId>docker-java</artifactId>
    <version>${docker-java.version}</version>
</dependency>
Gradle 依赖
dependencies {
    implementation 'com.github.docker-java:docker-java-core:3.4.1'
    implementation 'com.github.docker-java:docker-java-transport-httpclient5:3.4.1'
}

验证安装——30 秒看到结果:

HelloDocker.java
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Info;
import com.github.dockerjava.core.DockerClientBuilder;

public class HelloDocker {
    public static void main(String[] args) {
        // 自动检测本地 Docker(Unix Socket 或 TCP)
        DockerClient docker = DockerClientBuilder.getInstance().build();

        // 获取 Docker 信息(相当于 docker info)
        Info info = docker.infoCmd().exec();
        System.out.println("Docker 版本: " + info.getServerVersion());
        System.out.println("容器数量: " + info.getContainers());
        System.out.println("镜像数量: " + info.getImages());
    }
}

✅ 本节小结

  • 核心依赖 docker-java-core + 传输层实现
  • DockerClientBuilder.getInstance().build() 自动连接本地 Docker
  • docker-java 的 API 与 Docker CLI 命令一一对应

三、核心概念

🧱 类比:Docker-Java 就像一个「远程遥控器」
Docker Engine 是一台巨大的自动化工厂。DockerClient 是遥控器(连接工厂),每个 xxxCmd() 是一个遥控按钮(发送指令),.exec() 是按下按钮(执行),返回值是工厂的反馈。

DockerClient — 遥控器

import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;

// 方式 1:最简单——自动检测配置
DockerClient docker = DockerClientBuilder.getInstance().build();

// 方式 2:显式配置(连接远程 Docker)
var config = DefaultDockerClientConfig.createDefaultConfigBuilder()
        .withDockerHost("tcp://192.168.1.100:2375")    // 远程地址
        .withDockerTlsVerify(true)                      // TLS 验证
        .withDockerCertPath("/home/user/.docker/certs") // 证书路径
        .build();

var httpClient = new ApacheDockerHttpClient.Builder()
        .dockerHost(config.getDockerHost())
        .sslConfig(config.getSSLConfig())
        .build();

DockerClient docker = DockerClientImpl.getInstance(config, httpClient);

命令模式 API

Docker-Java 的所有操作都遵循同一个模式:docker.xxxCmd().withYyy(value).exec()

Docker CLIDocker-Java API
docker pull nginxdocker.pullImageCmd("nginx").exec(...)
docker create --name web nginxdocker.createContainerCmd("nginx").withName("web").exec()
docker start webdocker.startContainerCmd("web").exec()
docker exec web ls /docker.execCreateCmd("id").withCmd("ls","/").exec()
docker stop webdocker.stopContainerCmd("web").exec()
docker rm webdocker.removeContainerCmd("web").exec()

✅ 本节小结

  • DockerClient 是所有操作的入口
  • 所有 API 遵循 xxxCmd().withXxx().exec() 流畅模式
  • 支持本地 Unix Socket 和远程 TCP 连接

四、镜像管理

场景:Agent 需要准备执行环境——先确保需要的镜像存在,不存在就拉取。

import com.github.dockerjava.api.command.PullImageResultCallback;
import com.github.dockerjava.api.model.Image;

// 拉取镜像(阻塞等待完成)
docker.pullImageCmd("python")
      .withTag("3.12-slim")
      .exec(new PullImageResultCallback())
      .awaitCompletion();  // 等待拉取完成
System.out.println("Python 镜像拉取完成!");

// 列出本地镜像
List<Image> images = docker.listImagesCmd().exec();
for (Image img : images) {
    System.out.println(Arrays.toString(img.getRepoTags())
        + " - " + img.getSize() / 1024 / 1024 + "MB");
}

// 搜索 Docker Hub 上的镜像
var results = docker.searchImagesCmd("ubuntu").exec();
results.forEach(r -> System.out.println(r.getName() + " ⭐" + r.getStarCount()));

// 删除镜像
docker.removeImageCmd("python:3.12-slim").exec();
带进度回调的镜像拉取
import com.github.dockerjava.api.model.PullResponseItem;
import com.github.dockerjava.api.async.ResultCallback;

docker.pullImageCmd("node").withTag("20-alpine")
    .exec(new ResultCallback.Adapter<PullResponseItem>() {
        @Override
        public void onNext(PullResponseItem item) {
            if (item.getStatus() != null) {
                System.out.println(item.getStatus()
                    + (item.getProgress() != null ? " " + item.getProgress() : ""));
            }
        }
    })
    .awaitCompletion();

✅ 本节小结

  • pullImageCmd() 拉取镜像,awaitCompletion() 等待完成
  • listImagesCmd() 列出本地镜像
  • 支持进度回调,可以展示下载进度

五、容器生命周期

场景:Agent 决定运行一段 Python 代码——需要创建容器、启动、等待执行完毕、获取结果、最后清理。

import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.model.*;

// 1. 创建容器
CreateContainerResponse container = docker.createContainerCmd("python:3.12-slim")
        .withName("agent-sandbox-001")
        .withCmd("python", "-c", "print('Hello from Agent sandbox!')")
        // 资源限制(Agent 安全必备!)
        .withHostConfig(HostConfig.newHostConfig()
            .withMemory(256 * 1024 * 1024L)   // 内存上限 256MB
            .withCpuCount(1L)                   // 最多 1 个 CPU
            .withNetworkMode("none")            // 禁止网络访问
            .withAutoRemove(true))              // 执行完自动删除
        .exec();

String containerId = container.getId();
System.out.println("容器已创建: " + containerId.substring(0, 12));

// 2. 启动容器
docker.startContainerCmd(containerId).exec();

// 3. 等待容器执行完毕
int exitCode = docker.waitContainerCmd(containerId)
        .exec(new WaitContainerResultCallback())
        .awaitStatusCode();

System.out.println("退出码: " + exitCode); // 0 表示成功
容器完整生命周期管理
// 查看运行中的容器
List<Container> running = docker.listContainersCmd()
        .withShowAll(false)  // 仅运行中的
        .exec();

// 查看所有容器(包括已停止的)
List<Container> all = docker.listContainersCmd()
        .withShowAll(true)
        .exec();

// 暂停 / 恢复容器
docker.pauseContainerCmd(containerId).exec();
docker.unpauseContainerCmd(containerId).exec();

// 停止容器(发送 SIGTERM,10 秒后 SIGKILL)
docker.stopContainerCmd(containerId).withTimeout(10).exec();

// 强制杀死容器
docker.killContainerCmd(containerId).exec();

// 删除容器
docker.removeContainerCmd(containerId)
      .withForce(true)          // 强制删除运行中的容器
      .withRemoveVolumes(true)  // 同时删除关联的卷
      .exec();

// 检查容器详情
var info = docker.inspectContainerCmd(containerId).exec();
System.out.println("状态: " + info.getState().getStatus());
System.out.println("IP: " + info.getNetworkSettings()
        .getNetworks().values().iterator().next().getIpAddress());
⚠️ Agent 场景必须注意
一定要设置资源限制:内存、CPU、磁盘。否则恶意代码或死循环会耗尽宿主机资源。
一定要设置超时withStopTimeout() 防止容器永久运行。
推荐 withAutoRemove(true):容器停止后自动清理,避免残留。
推荐 withNetworkMode("none"):除非必要,否则禁止容器访问网络。

✅ 本节小结

  • 容器生命周期:create → start → (exec) → stop → remove
  • HostConfig 设置资源限制、网络、自动清理
  • waitContainerCmd() 阻塞等待容器结束并获取退出码

六、容器内执行命令(Exec)

场景:Agent 让一个长期运行的容器(如一个开发环境沙箱)执行各种命令——安装依赖、运行脚本、检查结果。这是 Agent 最核心的能力。

import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.command.ExecStartResultCallback;

// 假设容器已在运行
String containerId = "abc123...";

// 1. 创建 exec 实例
ExecCreateCmdResponse exec = docker.execCreateCmd(containerId)
        .withCmd("sh", "-c", "echo 'Hello' && ls /usr && whoami")
        .withAttachStdout(true)   // 捕获标准输出
        .withAttachStderr(true)   // 捕获错误输出
        .exec();

// 2. 执行并收集输出
StringBuilder stdout = new StringBuilder();
StringBuilder stderr = new StringBuilder();

docker.execStartCmd(exec.getId())
    .exec(new ResultCallback.Adapter<Frame>() {
        @Override
        public void onNext(Frame frame) {
            String text = new String(frame.getPayload());
            switch (frame.getStreamType()) {
                case STDOUT -> stdout.append(text);
                case STDERR -> stderr.append(text);
            }
        }
    })
    .awaitCompletion();

System.out.println("输出:\n" + stdout);
if (!stderr.isEmpty()) {
    System.err.println("错误:\n" + stderr);
}

// 3. 检查执行结果
var execInfo = docker.inspectExecCmd(exec.getId()).exec();
System.out.println("退出码: " + execInfo.getExitCodeLong());
封装为实用工具方法

在 Agent 中你会频繁执行命令,封装一个工具类很有必要:

public class DockerExecUtil {

    public record ExecResult(String stdout, String stderr, Long exitCode) {
        public boolean isSuccess() { return exitCode != null && exitCode == 0; }
    }

    public static ExecResult exec(DockerClient docker, String containerId,
                                  String... command) throws InterruptedException {
        var execCreate = docker.execCreateCmd(containerId)
                .withCmd(command)
                .withAttachStdout(true)
                .withAttachStderr(true)
                .exec();

        var stdout = new StringBuilder();
        var stderr = new StringBuilder();

        docker.execStartCmd(execCreate.getId())
            .exec(new ResultCallback.Adapter<Frame>() {
                @Override
                public void onNext(Frame frame) {
                    String text = new String(frame.getPayload());
                    switch (frame.getStreamType()) {
                        case STDOUT -> stdout.append(text);
                        case STDERR -> stderr.append(text);
                    }
                }
            })
            .awaitCompletion();

        var info = docker.inspectExecCmd(execCreate.getId()).exec();
        return new ExecResult(stdout.toString(), stderr.toString(),
                info.getExitCodeLong());
    }
}

// 使用
var result = DockerExecUtil.exec(docker, containerId,
        "python", "-c", "print(2 + 2)");
System.out.println(result.stdout());  // "4\n"
System.out.println(result.isSuccess()); // true
带超时的命令执行
import java.util.concurrent.TimeUnit;

// Agent 执行用户代码必须设置超时
docker.execStartCmd(exec.getId())
    .exec(new ResultCallback.Adapter<Frame>() { /* ... */ })
    .awaitCompletion(30, TimeUnit.SECONDS);  // 最多等 30 秒

// 如果超时返回 false
boolean finished = docker.execStartCmd(exec.getId())
    .exec(callback)
    .awaitCompletion(10, TimeUnit.SECONDS);

if (!finished) {
    System.err.println("命令执行超时!");
    // 可以考虑 kill 容器
    docker.killContainerCmd(containerId).exec();
}

✅ 本节小结

  • Exec = 两步操作:execCreateCmd() + execStartCmd()
  • 通过 Frame 回调分别捕获 stdout 和 stderr
  • 务必使用 awaitCompletion(timeout) 防止 Agent 卡死

七、日志与监控

场景:Agent 启动一个长运行服务后,需要监控其日志来判断是否正常工作。

import com.github.dockerjava.api.async.ResultCallback;

// 获取容器日志(一次性获取)
StringBuilder logs = new StringBuilder();
docker.logContainerCmd(containerId)
    .withStdOut(true)
    .withStdErr(true)
    .withTail(100)       // 最后 100 行
    .withTimestamps(true) // 包含时间戳
    .exec(new ResultCallback.Adapter<Frame>() {
        @Override
        public void onNext(Frame frame) {
            logs.append(new String(frame.getPayload()));
        }
    })
    .awaitCompletion();

System.out.println(logs);

// 实时流式日志(Follow 模式)
docker.logContainerCmd(containerId)
    .withStdOut(true)
    .withStdErr(true)
    .withFollowStream(true)  // 持续跟踪
    .withSince(0)
    .exec(new ResultCallback.Adapter<Frame>() {
        @Override
        public void onNext(Frame frame) {
            System.out.print("[实时] " + new String(frame.getPayload()));
        }
    });
// 注意:Follow 模式不会自动结束,需要自行 close
监控容器资源使用(Stats)
import com.github.dockerjava.api.model.Statistics;

// 获取容器实时资源统计
docker.statsCmd(containerId)
    .exec(new ResultCallback.Adapter<Statistics>() {
        @Override
        public void onNext(Statistics stats) {
            long memUsage = stats.getMemoryStats().getUsage();
            long memLimit = stats.getMemoryStats().getLimit();
            double memPercent = (double) memUsage / memLimit * 100;

            System.out.printf("内存: %.1f MB / %.1f MB (%.1f%%)%n",
                memUsage / 1e6, memLimit / 1e6, memPercent);
        }
    });

✅ 本节小结

  • logContainerCmd() 获取容器日志,支持 tail 和实时 follow
  • statsCmd() 实时监控容器的 CPU 和内存使用
  • Agent 可以通过分析日志来判断任务状态

八、网络与卷

卷(Volume)——数据持久化

// 创建卷
var volume = docker.createVolumeCmd()
        .withName("agent-workspace")
        .exec();

// 创建容器时挂载卷
docker.createContainerCmd("python:3.12-slim")
    .withHostConfig(HostConfig.newHostConfig()
        .withBinds(new Bind(
            "/host/path/data",         // 宿主机路径
            new Volume("/workspace"),   // 容器内路径
            AccessMode.rw))             // 读写权限
    )
    .exec();

// 使用命名卷(推荐)
docker.createContainerCmd("python:3.12-slim")
    .withHostConfig(HostConfig.newHostConfig()
        .withBinds(Bind.parse("agent-workspace:/workspace:rw"))
    )
    .exec();

// 删除卷
docker.removeVolumeCmd("agent-workspace").exec();

网络(Network)——容器互通

// 创建自定义网络
var network = docker.createNetworkCmd()
        .withName("agent-network")
        .withDriver("bridge")
        .exec();

// 将容器连接到网络
docker.connectToNetworkCmd()
    .withNetworkId("agent-network")
    .withContainerId(containerId)
    .exec();

// 同一网络中的容器可以通过容器名互访
// 例如:容器 A 可以用 http://container-b:8080 访问容器 B

✅ 本节小结

  • Volume 用于数据持久化和宿主机-容器文件共享
  • Network 让多个容器组成互联的微服务网络
  • Agent 场景中,Volume 常用于共享工作区

九、文件传输

场景:Agent 生成代码后需要把代码文件传入容器执行,执行完再把结果文件拿出来。

import org.apache.commons.compress.archivers.tar.*;
import java.io.*;

// ===== 文件写入容器 =====
// Docker API 要求以 tar 归档格式传输文件
public static void copyToContainer(DockerClient docker, String containerId,
                                    String content, String containerPath,
                                    String fileName) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try (TarArchiveOutputStream tar = new TarArchiveOutputStream(baos)) {
        byte[] bytes = content.getBytes();
        TarArchiveEntry entry = new TarArchiveEntry(fileName);
        entry.setSize(bytes.length);
        tar.putArchiveEntry(entry);
        tar.write(bytes);
        tar.closeArchiveEntry();
    }

    docker.copyArchiveToContainerCmd(containerId)
        .withTarInputStream(new ByteArrayInputStream(baos.toByteArray()))
        .withRemotePath(containerPath)
        .exec();
}

// 使用:把 Agent 生成的 Python 代码传入容器
copyToContainer(docker, containerId,
    "import math\nprint(math.pi)",
    "/workspace",
    "script.py");

// ===== 从容器读取文件 =====
InputStream stream = docker.copyArchiveFromContainerCmd(
        containerId, "/workspace/output.txt")
    .exec();

// 解析 tar 归档
try (TarArchiveInputStream tar = new TarArchiveInputStream(stream)) {
    TarArchiveEntry entry;
    while ((entry = tar.getNextEntry()) != null) {
        if (!entry.isDirectory()) {
            String content = new String(tar.readAllBytes());
            System.out.println("文件内容: " + content);
        }
    }
}
💡 Agent 实战技巧
Agent 的代码执行 Tool 通常流程为:生成代码 → copyArchiveToContainerCmd 传入 → execCreateCmd 执行 → copyArchiveFromContainerCmd 取回结果。这四步构成了完整的「沙箱代码执行」闭环。

✅ 本节小结

  • Docker API 要求以 tar 格式传输文件
  • copyArchiveToContainerCmd() 写入文件到容器
  • copyArchiveFromContainerCmd() 从容器读取文件

十、构建自定义镜像

场景:Agent 需要一个预装特定依赖的执行环境——直接从 Dockerfile 构建一个定制镜像。

import com.github.dockerjava.api.command.BuildImageResultCallback;
import java.io.File;
import java.util.Set;

// 假设 Dockerfile 在 ./sandbox-image/ 目录
String imageId = docker.buildImageCmd(new File("./sandbox-image/"))
        .withTags(Set.of("agent-sandbox:latest"))
        .withNoCache(true)
        .exec(new BuildImageResultCallback() {
            @Override
            public void onNext(BuildResponseItem item) {
                if (item.getStream() != null) {
                    System.out.print(item.getStream()); // 输出构建日志
                }
                super.onNext(item);
            }
        })
        .awaitImageId();

System.out.println("镜像构建完成: " + imageId);

配套的 Dockerfile 示例——Agent 沙箱环境:

Dockerfile
FROM python:3.12-slim

# 安装常用数据科学库
RUN pip install --no-cache-dir numpy pandas matplotlib requests

# 创建工作目录
WORKDIR /workspace

# 非 root 用户运行(安全)
RUN useradd -m sandbox
USER sandbox

CMD ["sleep", "infinity"]

✅ 本节小结

  • buildImageCmd() 从 Dockerfile 构建镜像
  • Agent 可以预构建包含特定依赖的沙箱镜像
  • 使用非 root 用户和最小权限原则提升安全性

十一、安全沙箱设计

场景:这是构建生产级 Agent 最关键的一章。当 Agent 执行 LLM 生成的代码时,你无法保证代码是安全的。必须从多个维度构建防护。

public class SecureSandbox {
    private final DockerClient docker;
    private final String sandboxImage;

    public SecureSandbox(DockerClient docker, String sandboxImage) {
        this.docker = docker;
        this.sandboxImage = sandboxImage;
    }

    /**
     * 创建一个安全的沙箱容器
     */
    public String createSandbox() {
        var container = docker.createContainerCmd(sandboxImage)
            .withHostConfig(HostConfig.newHostConfig()

                // 1. 内存限制(256MB,防止内存炸弹)
                .withMemory(256 * 1024 * 1024L)
                .withMemorySwap(256 * 1024 * 1024L) // 禁止 swap

                // 2. CPU 限制(1 核心)
                .withCpuCount(1L)
                .withCpuPeriod(100000L)
                .withCpuQuota(50000L)  // 50% CPU

                // 3. 禁止网络访问
                .withNetworkMode("none")

                // 4. 只读根文件系统
                .withReadonlyRootfs(true)
                // 但允许写入 /tmp 和 /workspace
                .withTmpFs(Map.of(
                    "/tmp", "size=64m",
                    "/workspace", "size=128m"))

                // 5. 禁止特权提升
                .withPrivileged(false)
                .withCapDrop(Capability.ALL)  // 移除所有 Linux 能力

                // 6. 限制进程数量(防止 fork 炸弹)
                .withPidsLimit(50L)

                // 7. 自动清理
                .withAutoRemove(true)
            )
            .withWorkingDir("/workspace")
            .exec();

        docker.startContainerCmd(container.getId()).exec();
        return container.getId();
    }

    /**
     * 在沙箱中安全执行代码
     */
    public ExecResult executeCode(String containerId, String code,
                                   String language, int timeoutSeconds)
                                   throws Exception {
        // 写入代码文件
        String fileName = switch (language) {
            case "python" -> "script.py";
            case "javascript" -> "script.js";
            case "bash" -> "script.sh";
            default -> "script.txt";
        };
        copyToContainer(docker, containerId, code, "/workspace", fileName);

        // 构建执行命令
        String[] cmd = switch (language) {
            case "python" -> new String[]{"python", "/workspace/script.py"};
            case "javascript" -> new String[]{"node", "/workspace/script.js"};
            case "bash" -> new String[]{"sh", "/workspace/script.sh"};
            default -> throw new IllegalArgumentException("不支持的语言: " + language);
        };

        // 带超时执行
        return DockerExecUtil.execWithTimeout(docker, containerId, cmd,
                timeoutSeconds);
    }

    /**
     * 销毁沙箱
     */
    public void destroy(String containerId) {
        try {
            docker.killContainerCmd(containerId).exec();
        } catch (Exception ignored) {}
        try {
            docker.removeContainerCmd(containerId).withForce(true).exec();
        } catch (Exception ignored) {}
    }
}
🛡️ 安全检查清单
内存限制:必须设置,且禁用 swap
CPU 限制:防止挖矿或死循环耗尽资源
网络隔离networkMode("none") 除非明确需要
只读文件系统readonlyRootfs(true) + tmpfs 白名单
非 root 用户:在 Dockerfile 中切换用户
进程数限制pidsLimit(50) 防 fork 炸弹
执行超时:所有命令必须有超时
自动清理autoRemove(true) 避免资源泄露

✅ 本节小结

  • 安全沙箱是生产级 Agent 的核心基础设施
  • 必须从内存、CPU、网络、文件系统、进程数多维度限制
  • 封装为 SecureSandbox 类,供 Agent Tool 调用

十二、综合实战:Code Execution Agent Tool

让我们把 Docker-Java 与 LangChain4j + LangGraph4j 集成起来,构建一个完整的 Agent 代码执行工具——AI 生成代码、Docker 安全执行、返回结果。

1

定义 LangChain4j Tool——让 Agent 可以执行代码

import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;

public class CodeExecutionTool {

    private final DockerClient docker;
    private final String sandboxImage = "agent-sandbox:latest";

    public CodeExecutionTool(DockerClient docker) {
        this.docker = docker;
    }

    @Tool("在安全沙箱中执行 Python 代码并返回结果。用于数学计算、数据处理等任务。")
    public String executePython(
            @P("要执行的 Python 代码") String code) {
        var sandbox = new SecureSandbox(docker, sandboxImage);
        String containerId = null;

        try {
            // 创建沙箱
            containerId = sandbox.createSandbox();

            // 执行代码(30 秒超时)
            var result = sandbox.executeCode(
                    containerId, code, "python", 30);

            if (result.isSuccess()) {
                return "执行成功:\n" + result.stdout();
            } else {
                return "执行失败(退出码 " + result.exitCode() + "):\n"
                    + result.stderr();
            }
        } catch (Exception e) {
            return "执行异常: " + e.getMessage();
        } finally {
            if (containerId != null) {
                sandbox.destroy(containerId);
            }
        }
    }

    @Tool("在安全沙箱中执行 Bash 命令并返回结果")
    public String executeBash(
            @P("要执行的 Bash 命令") String command) {
        var sandbox = new SecureSandbox(docker, sandboxImage);
        String containerId = null;

        try {
            containerId = sandbox.createSandbox();
            var result = sandbox.executeCode(
                    containerId, command, "bash", 15);
            return result.isSuccess()
                ? result.stdout()
                : "错误: " + result.stderr();
        } catch (Exception e) {
            return "执行异常: " + e.getMessage();
        } finally {
            if (containerId != null) sandbox.destroy(containerId);
        }
    }
}
2

组装 Agent——用 LangChain4j AI Services 或 LangGraph4j AgentExecutor

import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;

// 方式 A:用 LangChain4j AI Services
interface CodeAssistant {
    @SystemMessage("""
        你是一个编程助手。当用户需要计算、数据处理或验证代码时,
        使用 executePython 工具在沙箱中运行代码。
        始终先编写代码,然后执行,最后解释结果。
        """)
    String chat(String message);
}

var model = OpenAiChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("gpt-4o-mini")
        .build();

var docker = DockerClientBuilder.getInstance().build();

CodeAssistant assistant = AiServices.builder(CodeAssistant.class)
        .chatLanguageModel(model)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
        .tools(new CodeExecutionTool(docker))
        .build();

// 测试
System.out.println(assistant.chat("计算前 100 个质数的和"));
// Agent 会:
// 1. 生成 Python 代码(筛选质数并求和)
// 2. 调用 executePython Tool → Docker 沙箱执行
// 3. 获取结果 → 组织自然语言回复

System.out.println(assistant.chat("用 matplotlib 画一个 sin 函数的图"));
3

方式 B:用 LangGraph4j AgentExecutor(ReACT 循环)

import org.bsc.langgraph4j.agentexecutor.AgentExecutor;

// 如果代码执行失败,ReACT Agent 会自动分析错误、修改代码、重试
var agent = AgentExecutor.builder()
        .chatModel(model)
        .toolsFromObject(new CodeExecutionTool(docker))
        .build()
        .compile();

for (var step : agent.stream(Map.of("messages",
        "读取 /etc/os-release 文件内容,告诉我沙箱运行的是什么操作系统"))) {
    System.out.println(step);
}
// Agent 会多次尝试:先用 Python,如果不行就用 Bash
// ReACT 循环让它能从错误中学习
🎉 三篇教程的完美串联
这个项目把三篇教程的核心技术完全打通了:
LangChain4j(第一篇):AI Services、@Tool 注解、Chat Memory
LangGraph4j(第二篇):AgentExecutor、ReACT 循环、状态管理
Docker-Java(第三篇):安全沙箱、代码执行、文件传输

它们共同构成了一个能安全执行代码的 AI Agent

✅ 本节小结

  • @Tool 注解把 Docker 沙箱封装为 Agent 的执行能力
  • AI Services 或 AgentExecutor 均可驱动代码执行
  • ReACT 模式让 Agent 能从执行错误中自动修正

十三、速查表 / Cheat Sheet

连接与信息

API用途
DockerClientBuilder.getInstance().build()自动连接本地 Docker
docker.infoCmd().exec()获取 Docker 系统信息
docker.pingCmd().exec()检测 Docker 是否可用
docker.versionCmd().exec()获取 Docker 版本

镜像操作

API对应 CLI
docker.pullImageCmd("img").withTag("tag").exec(cb)docker pull img:tag
docker.listImagesCmd().exec()docker images
docker.removeImageCmd("img").exec()docker rmi img
docker.buildImageCmd(dir).withTags(set).exec(cb)docker build -t tag .
docker.searchImagesCmd("q").exec()docker search q

容器操作

API对应 CLI
docker.createContainerCmd("img").withName("n").exec()docker create --name n img
docker.startContainerCmd(id).exec()docker start id
docker.stopContainerCmd(id).exec()docker stop id
docker.killContainerCmd(id).exec()docker kill id
docker.removeContainerCmd(id).withForce(true).exec()docker rm -f id
docker.listContainersCmd().withShowAll(true).exec()docker ps -a
docker.inspectContainerCmd(id).exec()docker inspect id
docker.waitContainerCmd(id).exec(cb)docker wait id

Exec 与日志

API对应 CLI
docker.execCreateCmd(id).withCmd(...).exec()docker exec id ...(创建)
docker.execStartCmd(execId).exec(cb)docker exec(执行)
docker.logContainerCmd(id).withStdOut(true).exec(cb)docker logs id
docker.statsCmd(id).exec(cb)docker stats id

文件、网络、卷

API对应 CLI
docker.copyArchiveToContainerCmd(id).exec()docker cp file id:/path
docker.copyArchiveFromContainerCmd(id, path).exec()docker cp id:/path .
docker.createNetworkCmd().withName("n").exec()docker network create n
docker.createVolumeCmd().withName("v").exec()docker volume create v

十四、进阶路线图

Docker Compose 集成:编排多容器服务(数据库 + 后端 + 前端),Agent 可以管理完整应用栈

Testcontainers:Docker-Java 的兄弟项目,专为集成测试设计,Agent 可以用它来验证代码

容器健康检查:配置 Health Check,Agent 自动监控和修复故障服务

镜像缓存策略:预热常用镜像、多阶段构建优化,减少 Agent 冷启动时间

Kubernetes 集成:从 Docker 扩展到 K8s,Agent 管理集群级别的基础设施

安全审计:记录所有 Agent 的 Docker 操作,用于合规和事后分析

推荐资源

资源链接说明
Docker-Java GitHubgithub.com/docker-java源码与示例
Docker Engine APIdocs.docker.com/engine/api底层 REST API 文档
Testcontainerstestcontainers.com基于 Docker 的集成测试框架
Baeldung 教程Docker Guide for Java详细的 API 参考

🎓 系列教程第三篇完成!

三篇教程共同构成了 Java AI Agent 的完整技术栈:
LangChain4j(LLM 调用)+ LangGraph4j(工作流编排)+ Docker-Java(安全执行)

现在去构建你的 AI Agent 吧!