用 Docker 容器化 Claude Code 实现安全可控的 AI 编程沙盒

1. 项目概述:为什么你必须把 Claude Code 关进 Docker 这个“玻璃房”

我第一次被 Claude Code “温柔地”改掉 .env 文件时,手是抖的。它删掉了三行我用来连接测试数据库的配置,理由是“未在代码中显式引用,疑似废弃”。那三行确实没被 import 过,但它们被一个用 os.getenv() 动态加载的初始化脚本读取——而那个脚本,恰好没被 Claude Code 的静态分析扫描到。更讽刺的是,它还顺手给 README.md 加了段注释:“已清理冗余环境变量”,语气像极了刚做完大扫除、叉腰站在门口等表扬的室友。

这件事让我彻底放弃了“信任 AI 会自己判断边界”的幻想。不是它不聪明,而是它的聪明没有天然的护栏。它能读你整个硬盘,能执行 rm -rf ,能 git commit --amend 覆盖历史,也能 pip install --force-reinstall 把你生产环境的依赖链搅成一锅粥。它的能力是通用的,但你的项目不是。你需要的不是更强的模型,而是一套 可预测、可撤销、可审计的执行沙盒

Docker 就是这个沙盒。它不是什么新奇技术,而是我们这代开发者最熟悉、最可靠的隔离机制。它不靠玄学权限控制,不靠复杂策略引擎,就靠一层轻量级的内核隔离——进程看不见彼此,文件系统互不交叉,网络栈各自独立。当你把 Claude Code 放进容器,你得到的不是“一个运行中的 CLI 工具”,而是一个 有明确输入、明确输出、明确生命周期的原子化工作单元 。你传给它的,只有你主动挂载进去的那几行代码;它能修改的,只有你授权它写入的那几个文件;它运行完,整个临时环境连同所有中间产物,一键 --rm ,干干净净,不留一丝痕迹。

这背后是三个不可替代的价值: 安全兜底、环境复现、协作一致 。安全兜底,意味着你再也不用盯着终端每一条输出,生怕它下一秒就 chmod 777 / ;环境复现,意味着你今天在 macOS 上跑通的重构任务,明天同事在 Windows 上拉下代码、 docker compose up ,结果分毫不差;协作一致,则意味着团队里每个人用的不是自己电脑上七拼八凑的 Node 版本、Python 环境、CLI 配置,而是一份写死在 Dockerfile 里的、经过 QA 验证的黄金镜像。这不是过度工程,这是把“让 AI 帮你写代码”这件事,从一次性的、高风险的实验,变成一条可嵌入 CI/CD 流水线、可纳入代码审查流程、可写进团队开发规范的常规操作。

所以,这篇指南不讲“Claude Code 多厉害”,也不堆砌 API 文档。它只讲一件事: 如何亲手搭建一个牢靠、高效、零歧义的容器化执行环境 。从最简陋的单容器沙盒,到能和数据库、Web 服务实时对话的 Compose 编排,再到本地模型推理与外部工具集成的扩展路径。每一个命令、每一行配置、每一个 --rm -v 参数,背后都有我踩过的坑、测过的数据、推演过的失败场景。你不需要相信我的结论,你只需要照着做,然后亲眼看到 cat app.py 输出的 docstring,或者 docker compose logs -f db 里 PostgreSQL 成功启动的日志——那一刻,你会明白,为什么说“把 AI 关进玻璃房”,才是让它真正为你所用的第一步。

2. 核心设计思路:为什么是 Docker,而不是其他方案

2.1 容器化不是选择题,而是安全底线

很多人问:“我直接在宿主机装 npm install -g @anthropic-ai/claude-code 不行吗?更快、更简单。” 行,当然行。就像你可以徒手拆一台正在运转的服务器电源,只要动作够快,理论上不会触电。但问题从来不在“能不能”,而在“值不值得冒这个险”。

Claude Code 的本质,是一个拥有完整 shell 权限的、基于自然语言指令的自动化脚本引擎。它内部封装了 fs.readFile , child_process.execSync , git.commit 等一系列高危操作。当它运行在你的宿主机上,它的“工作空间”就是你的整个 $HOME 目录。 .ssh/config 里存着的跳板机密钥、 ~/.aws/credentials 里的云平台凭证、 /etc/hosts 里为本地开发加的域名映射——这些对人类开发者至关重要的资产,在 Claude Code 的眼里,和 app.py 里的一个函数一样,只是“可读取的文本文件”。它不会因为你没在 prompt 里提,就自动忽略它们。它只会忠实地执行你告诉它“检查项目配置”的指令,然后顺手把 ~/.aws/credentials 也列出来,甚至可能因为某条模糊的指令(比如“清理所有过期的配置”)而把它删掉。

Docker 提供的,是操作系统级别的硬隔离。它的核心保护机制有三层,缺一不可:

  1. 文件系统隔离(Filesystem Isolation) :这是最根本的一层。容器默认只能看到自己镜像里的文件和你显式挂载( -v )进来的目录。 /root , /home/otheruser , /etc/shadow 这些路径,在容器里压根不存在。你挂载 $(pwd):/workspace ,它就只能碰 ./app.py ./README.md ;你不挂载 ~/.ssh ,它连 ls ~/.ssh 都会报 Permission denied 。这种隔离不是靠 .gitignore 那种软性约定,而是内核 mount namespace 的硬性限制,连 strace 都无法绕过。

  2. 进程隔离(Process Isolation) :容器内的 ps aux 只能看到自己 PID namespace 里的进程。它永远看不到你宿主机上正在跑的 VS Code、Chrome、或者那个偷偷在后台同步网盘的进程。这意味着它无法通过 kill -9 干扰你的日常工作流,也无法通过 lsof -i :3000 发现你本地起的另一个开发服务器并试图“优化”它。

  3. 网络隔离(Network Isolation) :默认情况下,容器使用 bridge 网络,它有自己的 IP 段(如 172.17.0.0/16 ),通过 NAT 访问外网。它无法直接访问宿主机的 127.0.0.1:5432 (PostgreSQL),除非你显式用 --network host --add-host 打破这层隔离。这层隔离,恰恰是防止“AI 代理意外连上生产数据库”的最后一道闸门。

这三层隔离共同构成了一条清晰的“信任边界”。你信任的,不是 Claude Code 的代码逻辑是否完美,而是 Linux 内核的 namespaces cgroups 是否可靠——而后者,是经过二十年生产环境千锤百炼的基石。把 AI 放进容器,本质上是把“对模型的信任”,降级为“对操作系统内核的信任”,这是一个极其明智的风险转移。

2.2 为什么不是 VM、Sandbox 或其他沙盒方案

有人会提:“VM 不是更隔离吗?用 QEMU/KVM 开个虚拟机,岂不是绝对安全?” 理论上是的,但实践上是灾难。启动一个轻量级 VM 需要几百 MB 内存、数秒冷启动时间,而一个 Docker 容器是毫秒级启动、内存占用仅几十 MB。Claude Code 的典型任务是“读 5 个文件 -> 思考 10 秒 -> 写 1 个文件”,你不可能为每次思考都开一个 VM。这就像为了查一个单词,先买台新电脑、装好系统、再打开词典软件——效率归零。

另一种常见方案是 firejail bubblewrap 这类用户态沙盒。它们确实轻量,但成熟度和生态支持远不如 Docker。 firejail 的配置语法晦涩, bubblewrap 的文档稀少,更重要的是,它们缺乏 Docker 那样庞大的镜像仓库、成熟的 CI/CD 集成、以及 docker compose 这种开箱即用的多服务编排能力。当你需要让 Claude Code 同时和一个 Python Web 服务、一个 PostgreSQL 数据库交互时, firejail --net=none --private=... 这种命令行参数会迅速膨胀成一张无法维护的纸。

还有人尝试用 nix-shell conda env 做环境隔离。这能解决依赖冲突,但完全无法解决文件系统和进程隔离。 nix-shell 里的 rm -rf / 依然能删掉你宿主机的根目录, conda activate myenv 里的 git commit 依然会直接提交到你宿主机的 Git 仓库。它们是“环境管理器”,不是“执行沙盒”。

Docker 是唯一一个同时满足四个硬性条件的方案: 轻量(毫秒启动)、标准(OCI 兼容)、生态完备(Compose, Registry, Desktop)、隔离彻底(内核级 namespaces) 。它不是最炫酷的,但它是经过无数工程师用血泪验证过的、在“安全”与“效率”之间找到的那个黄金平衡点。

2.3 单容器 vs. Docker Compose:你的任务决定了架构的复杂度

很多教程一上来就甩出一个巨复杂的 docker-compose.yml ,里面塞了七八个服务。这反而会吓退初学者。事实上,90% 的 Claude Code 使用场景,根本用不到 Compose。你需要的,只是一个干净、独立、一次性的“代码编辑间”。

  • 单容器沙盒( docker run :这是你的默认起点,也是最安全的模式。它适用于:

    • 对单个代码库进行一次性重构(如“把所有 var 替换为 const ”)
    • 为某个模块自动生成单元测试
    • 快速阅读并总结一个陌生项目的结构
    • 在 CI 流水线中,作为 test 阶段的一个检查项(如“运行 claude 'check for security anti-patterns' ”)

    它的核心优势是 极致简单 :一条命令,一个镜像,一个挂载点,任务结束,容器消失。没有状态残留,没有网络配置,没有服务依赖。你甚至可以把它写成一个 shell 函数, alias claude='docker run --rm -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY -v $(pwd):/workspace -u $(id -u):$(id -g) claude-code:latest --dangerously-skip-permissions' ,然后像调用原生命令一样使用。

  • Docker Compose :当你开始处理“真实世界”的开发任务时,它才登场。真实世界的代码,很少是孤岛。它通常依赖:

    • 一个运行中的数据库(PostgreSQL, MySQL)
    • 一个本地 API 服务(Node.js, Python FastAPI)
    • 一个前端开发服务器(Vite, Webpack Dev Server)
    • 一个消息队列(Redis, RabbitMQ)

    此时,Claude Code 的任务就变成了“端到端的集成调试”。例如:“检查 web 服务能否成功连接 db ,如果失败,请根据错误日志定位问题并修复 web 的数据库连接配置”。这个任务,要求 claude 服务不仅能读写代码,还能 curl http://web:8000/health ,能 psql -h db -U user appdb -c '\dt' 。这就必须用 Compose,因为它能:

    • 创建一个私有内部网络( app-network ),让 claude , web , db 通过服务名( web , db )互相发现和通信。
    • 定义健康检查( healthcheck ),确保 claude 不会在 db 还没启动完成时就贸然发起连接,避免一堆无意义的超时错误。
    • 统一管理生命周期( docker compose up -d , docker compose down ),而不是手动 docker run 一堆服务再 docker stop

    选择哪种,不取决于你“想不想用高级功能”,而取决于你手头的任务是否真的需要跨服务协作。从单容器起步,等你遇到第一个“Claude Code 说找不到数据库”的报错时,再引入 Compose,这才是符合认知规律的学习路径。

3. 核心细节解析:构建一个安全、可靠、可复现的镜像

3.1 Dockerfile 的每一行,都是一个安全决策

下面这个看似简单的 Dockerfile ,是我反复迭代十几次后定稿的,每一行背后都有一个具体的失败案例:

FROM node:20-slim

# 创建非 root 用户,强制最小权限
RUN useradd -m -u 1001 claude

# 全局安装 CLI,但不使用 root 权限
RUN npm install -g @anthropic-ai/claude-code

# 设置工作目录,所有操作在此发生
WORKDIR /workspace

# 切换到非 root 用户,这是最关键的一步
USER claude

# 入口点,直接暴露 CLI
ENTRYPOINT ["claude"]

让我们逐行拆解其设计哲学:

  • FROM node:20-slim :选择 slim 而非 alpine ,是血泪教训。 alpine 使用 musl libc ,而 @anthropic-ai/claude-code 的某些底层依赖(尤其是涉及文件系统监控或二进制工具调用的部分)与 musl 不兼容。我第一次用 alpine 构建时, docker run 启动就报 Error: /usr/lib/libc.musl-x86_64.so.1: version GLIBC_2.34 not found 。这个错误信息极其晦涩,花了我整整一个下午才定位到 musl vs glibc 的 ABI 不兼容问题。 slim 镜像是基于 Debian 的,使用标准 glibc ,兼容性极佳,体积(约 180MB)也远小于 full (约 1GB),是完美的平衡点。

  • RUN useradd -m -u 1001 claude :创建一个 UID 为 1001 的非 root 用户。为什么是 1001 ?因为这是 Docker Desktop 在 macOS 和 Windows 上默认分配给第一个非 root 用户的 UID。如果你用 1000 ,在某些旧版 Docker Desktop 上可能会与宿主机的 daemon 用户冲突。更重要的是, -m 参数会为该用户创建家目录( /home/claude ),这能避免某些 CLI 工具(如 git )因找不到 $HOME 而崩溃。

  • RUN npm install -g @anthropic-ai/claude-code :全局安装 CLI。这里有个关键点: npm install -g 默认会将二进制文件放在 /usr/local/bin ,而 /usr/local node:slim 镜像里预设的、普通用户有写权限的路径。这保证了后续 USER claude 切换后,CLI 依然能被正确执行。如果换成 yarn global add ,它可能会把二进制放到 /root/.yarn/bin ,而 claude 用户根本无权访问 /root

  • USER claude :这是整个安全模型的基石。一旦执行此指令,后续所有命令(包括 ENTRYPOINT )都将以 claude 用户身份运行。这意味着:

    • 它无法 apt-get update 安装新包(没有 sudo 权限)
    • 它无法 chown 修改文件所有权(没有 root 权限)
    • 它无法 mount 新的文件系统(没有 CAP_SYS_ADMIN 能力)
    • 它的 ps aux 只能看到自己用户的进程

    即使容器内存在漏洞,攻击者也只能以 claude 用户身份进行横向移动,而无法获得 root 权限来逃逸到宿主机。这是一种纵深防御(Defense in Depth)思想。

  • ENTRYPOINT ["claude"] :使用 exec 形式的 ENTRYPOINT(方括号语法),而非 shell 形式( ENTRYPOINT claude )。前者会让 claude 进程成为容器的 PID 1,从而能正确接收 SIGTERM 信号,实现优雅退出。后者会启动一个 sh -c 进程作为 PID 1,而 sh 默认会忽略 SIGTERM ,导致 docker stop 命令超时后强制 kill -9 ,可能中断正在写入的文件操作。

3.2 构建与缓存:让每一次 docker build 都快如闪电

Docker 的分层缓存(Layer Caching)是提升开发效率的核心。理解它,就能把 30 秒的构建缩短到 1 秒。关键原则是: 把变化频率低的指令放在前面,变化频率高的指令放在后面

看这个优化前的 Dockerfile

FROM node:20-slim
WORKDIR /workspace
COPY package.json .
RUN npm install
COPY . .
ENTRYPOINT ["claude"]

问题在于: COPY . . 是最后一步,但 . 目录下的任何微小改动(比如改一行 README.md ),都会让 COPY . . 这一层缓存失效,导致 npm install 也必须重新执行——即使 package.json 根本没变。

优化后的版本(多阶段构建):

# 构建阶段:只负责安装依赖
FROM node:20-slim AS builder
WORKDIR /app
COPY package.json .
RUN npm install -g @anthropic-ai/claude-code

# 运行阶段:只包含运行时所需
FROM node:20-slim
# 从构建阶段复制已安装的 CLI
COPY --from=builder /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=builder /usr/local/bin/claude /usr/local/bin/claude
# 创建非 root 用户
RUN useradd -m -u 1001 claude
USER claude
WORKDIR /workspace
ENTRYPOINT ["claude"]

这个版本的缓存逻辑是:

  • FROM node:20-slim 层:几乎永不变化,永远命中缓存。
  • COPY package.json . 层:只有 package.json 改了才失效。
  • RUN npm install ... 层:只有 package.json 改了才重新执行。
  • COPY --from=builder ... 层:只要构建阶段没变,就永远命中。

实测数据:在一个典型的项目中,首次构建耗时 42 秒。之后,即使你修改了 Dockerfile 中的 WORKDIR USER 指令,由于这些指令在运行阶段且位于缓存链末端,构建时间仍稳定在 1.2 秒左右。这是因为 COPY --from=builder 这一层是最大的缓存块,它复用了之前构建好的、完整的 CLI 环境。

提示:永远在 Dockerfile 顶部添加 # syntax=docker/dockerfile:1 。这启用了最新的 Dockerfile 语法(如 --mount=type=cache ),为未来升级预留空间。

3.3 权限与挂载:让容器内的文件修改,精准映射到宿主机

这是新手最容易卡住的环节。你运行了 docker run ,Claude Code 也成功生成了代码,但 cat app.py 看不到任何变化?99% 的原因是权限不匹配。

根本原因在于:Linux 文件系统通过 UID/GID(用户/组 ID)来标识所有权。宿主机上,你的用户 john 的 UID 是 1000 ,GID 是 1000 。而容器里, claude 用户的 UID 是 1001 ,GID 是 1001 。当你用 -v $(pwd):/workspace 挂载目录时,容器内 /workspace/app.py 的所有权,依然是宿主机 john (UID 1000)的。 claude 用户(UID 1001)试图写入,就会收到 Permission denied

解决方案是 --user (或 -u )参数:

docker run --rm \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  -v $(pwd):/workspace \
  -u $(id -u):$(id -g) \  # 👈 关键!把宿主机的 UID/GID 透传进去
  claude-code:latest \
  --dangerously-skip-permissions \
  "Add a docstring to the hello function in app.py."

$(id -u) $(id -g) 会动态获取当前 shell 的 UID/GID。这样,容器内运行的进程,其有效 UID/GID 就和宿主机上文件的所有者完全一致,写入操作自然畅通无阻。

注意: -u 参数必须在 -v 之后指定,否则 Docker 会报错。这是一个容易忽略的顺序陷阱。

另一个常见误区是挂载路径。很多人习惯性地写 -v ./workspace:/workspace ,以为这样更“规范”。但 ./workspace 是一个子目录,如果它不存在,Docker 会自动创建一个空目录,然后挂载进去。结果就是,Claude Code 启动后, ls /workspace 是空的,它根本看不到你项目里的任何文件。正确的做法永远是 -v $(pwd):/workspace ,即挂载当前工作目录( $PWD ),这是最直接、最不易出错的方式。

4. 实操过程:从零开始,一步步跑通你的第一个容器化任务

4.1 环境准备:三分钟确认一切就绪

在敲下第一行 docker build 之前,请务必花三分钟做以下检查。这能帮你避开 80% 的“教程跑不通”问题。

第一步:确认 Docker Daemon 健康

# 这是最基础的“心跳检测”
docker run --rm hello-world

如果输出 Hello from Docker! ,说明 Docker 引擎正常。如果报错 Cannot connect to the Docker daemon... ,请检查:

  • macOS/Windows:Docker Desktop 是否已启动?
  • Linux:你的用户是否在 docker 组里?运行 sudo usermod -aG docker $USER ,然后 完全退出并重新登录终端 su - $USER 不够,必须重启 shell)。

第二步:确认 Node.js 版本

node --version
# 必须 >= v18.0.0。Claude Code CLI 依赖较新的 V8 引擎特性。

如果版本过低,去 Node.js 官网 下载 LTS 版本安装。

第三步:获取并设置 Anthropic API Key

  1. 登录 Anthropic Console
  2. 进入 API Keys 页面,点击 Create Key ,命名为 claude-docker-tutorial
  3. 将生成的密钥(形如 sk-ant-api03-... )安全地保存。
  4. 在你的 shell 配置文件( ~/.zshrc ~/.bashrc )中添加:
    export ANTHROPIC_API_KEY="sk-ant-api03-..."
    
  5. 执行 source ~/.zshrc (或 source ~/.bashrc )使其生效。
  6. 验证: echo $ANTHROPIC_API_KEY 应该输出你的密钥。 切记,永远不要把这个密钥写进 Dockerfile docker-compose.yml

第四步:创建一个干净的测试工作区

mkdir ~/claude-docker-tutorial
cd ~/claude-docker-tutorial
echo "# My First Dockerized Agent" > README.md
echo "def hello(): return 'world'" > app.py
echo "print(hello())" > main.py

这个目录就是你的“沙盒战场”。它只包含你要让 Claude Code 操作的代码,没有任何 .git , .env , node_modules 等干扰项。保持它的纯粹,是避免意外的首要原则。

4.2 构建镜像:见证你的第一个 claude-code:latest

进入 ~/claude-docker-tutorial 目录,创建 Dockerfile (内容见 3.1 节),然后执行:

# 构建镜像,打上标签
docker build -t claude-code:latest .

# 查看镜像列表,确认构建成功
docker images | grep claude-code
# 输出应类似:claude-code         latest     123abcde1234   2 minutes ago   210MB

首次构建会下载 node:20-slim 基础镜像(约 180MB),并安装 CLI,耗时约 30-45 秒。之后,只要你没改 Dockerfile 的前几行,再次构建会显示 Using cache ,瞬间完成。

实操心得:在 Dockerfile 末尾加一行 RUN claude --version 。这样,构建过程会自动验证 CLI 是否安装成功。如果安装失败,构建会直接报错,而不是让你等到 docker run 时才发现 command not found

4.3 运行沙盒:执行你的第一个读写任务

现在,万事俱备。让我们执行两个经典任务:

任务一:只读任务(安全,无需额外参数)

docker run --rm \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  -v $(pwd):/workspace \
  claude-code:latest \
  "List all files in this workspace and describe their purpose in one sentence each."

这个命令会:

  • 启动一个容器,挂载当前目录到 /workspace
  • ANTHROPIC_API_KEY 作为环境变量注入。
  • --rm 确保容器退出后自动删除。
  • Claude Code 会读取 README.md , app.py , main.py ,然后生成一份描述。

任务二:写入任务(需要权限透传)

docker run --rm \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  -v $(pwd):/workspace \
  -u $(id -u):$(id -g) \
  claude-code:latest \
  --dangerously-skip-permissions \
  "Add a comprehensive docstring to the hello() function in app.py, explaining its input, output, and side effects."

执行后,立刻检查:

cat app.py

你应该看到 app.py 的内容已经更新,顶部多了一段详细的 docstring。这证明:

  • -u $(id -u):$(id -g) 成功透传了权限。
  • --dangerously-skip-permissions 正确跳过了交互式确认。
  • -v $(pwd):/workspace 的双向挂载工作正常。

注意事项:“ --dangerously-skip-permissions ” 这个 flag 名字听起来很吓人,但它在这里是完全安全的。它的“危险”之处在于,它跳过了 Claude Code 自带的、针对宿主机文件系统的二次确认。但在容器里,这个确认本身就是多余的——因为容器的文件系统隔离已经确保了它只能修改你挂载进去的那几个文件。所以,这个 flag 的作用,不是“降低安全性”,而是“移除一个在容器环境下毫无意义的、阻碍自动化的障碍”。

4.4 进阶实战:用 Docker Compose 编排一个多服务开发环境

现在,让我们模拟一个真实的后端开发场景:一个 Python Web 服务( web )需要连接一个 PostgreSQL 数据库( db ),而你想让 Claude Code 来帮你诊断和修复连接问题。

~/claude-docker-tutorial 目录下,创建 docker-compose.yml

services:
  claude:
    build: .
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
    volumes:
      - .:/workspace
    # 显式声明 UID/GID,确保文件写入权限
    user: "${UID}:${GID}"
    # 依赖 db 服务,并等待其健康
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-network

  web:
    image: python:3.12-slim
    working_dir: /app
    volumes:
      - .:/app
    # 一个极简的 Web 服务,监听 8000 端口
    command: python -c "from http.server import HTTPServer, BaseHTTPRequestHandler; \
      class H(BaseHTTPRequestHandler): \
        def do_GET(self): self.send_response(200); self.end_headers(); self.wfile.write(b'OK'); \
      HTTPServer(('0.0.0.0:8000', 8000), H).serve_forever()"
    ports:
      - "8000:8000"
    networks:
      - app-network

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    healthcheck:
      # PostgreSQL 的健康检查命令,比 ping 更可靠
      test: ["CMD-SHELL", "pg_isready -U user -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

然后,执行以下步骤:

  1. 导出 UID/GID (Compos e 需要它们):

    export UID=$(id -u) GID=$(id -g)
    
  2. 启动数据库和 Web 服务(后台运行)

    docker compose up -d web db
    
  3. 观察日志,确认服务健康

    docker compose logs -f db
    # 等待出现 "database system is ready to accept connections" 后,Ctrl+C 退出
    
  4. 运行 Claude Code 进行诊断

    docker compose run --rm claude \
      --dangerously-skip-permissions \
      "The web service is failing to connect to the database. Check the connection string and the database health. If the database is healthy, suggest how to fix the web service's connection code in main.py."
    

这个流程的关键在于 depends_on + condition: service_healthy 。它确保了 claude 服务在启动时, db 服务不仅已经 up ,而且其 pg_isready 健康检查已经通过。我曾经跳过这一步, claude 一启动就去 psql -h db ,结果得到一堆 Connection refused ,浪费了 20 分钟排查网络配置,最后才发现是 db 还在初始化。

实操心得:在 docker-compose.yml 里,永远为数据库服务加上 healthcheck 。这是 Docker Compose 最被低估、却最有价值的功能之一。它把“服务是否可用”这个模糊概念,转化成了一个可编程、可依赖、可自动重试的精确信号。

5. 常见问题与排查技巧实录:那些让我熬夜的 Bug 和解法

5.1 网络问题:Claude Code 说“API 连接超时”,但你的浏览器能打开 Anthropic 网站

这是最让人抓狂的问题。症状是: docker run 命令卡住,几秒后报错 Request failed with status code 403 connect ETIMEDOUT api.anthropic.com:443 。而你在宿主机上 curl https://api.anthropic.com 完全正常。

排查思路 :容器的网络栈是独立的。它可能被公司代理、防火墙或 Docker 自身的网络配置拦截。

速查命令

# 1. 在容器内测试基础网络连通性
docker run --rm alpine wget -qO- http://www.google.com

# 2. 在容器内测试 DNS 解析
docker run --rm alpine nslookup api.anthropic.com

# 3. 在容器内测试目标端口连通性(关键!)
docker run --rm alpine telnet api.anthropic.com 443
# 如果这里卡住或报错 "Connection refused",说明网络层不通

解决方案

  • 公司代理 :如果你的公司网络需要代理,Docker 守护进程本身也需要配置。在 Docker Desktop 的 Settings -> Resources -> Proxies 中设置 HTTP/HTTPS 代理。Linux 用户则需编辑 /etc/docker/daemon.json ,添加 "proxies" 字段。
  • Docker 网络损坏 :重置 Docker 网络。 docker network prune 清理所有未使用的网络,然后重启 Docker Desktop 或 sudo systemctl restart docker
  • DNS 问题 :在 docker run 命令中显式指定 DNS 服务器: --dns 8.8.8.8 --dns 1.1.1.1

5.2 文件系统问题:“Claude Code 说找不到 app.py”,但 ls 显示它就在那里

症状: docker run 启动后,Claude Code 立即报错 Error: ENOENT: no such file or directory, open '/workspace/app.py' 。而你在宿主机上 ls app.py 清晰可见。

排查思路 :挂载路径错了,或者挂载的目录是空的。

速查命令

# 在容器内直接列出挂载点的内容
docker run --rm -v $(pwd):/workspace alpine ls -la /workspace

# 如果输出是空的,或者只有一行 "total 0",说明挂载失败。
# 检查当前目录是否存在且有读取权限
ls -ld $(pwd)
# 输出应类似:drwxr-xr-x 3 john john 96 May 1 10:00 /Users/john/claude-docker-tutorial
# 如果权限是 `drwx------`,说明其他用户(包括 docker)无权进入,需要 `chmod 755 $(pwd)`

解决方案

  • 路径错误 :确保 $(pwd) 输出的是你期望的绝对路径。在脚本中,最好用 $(realpath .) 代替 `$(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值