1. 项目概述:为什么你必须把 Claude Code 关进 Docker 这个“玻璃房”
我第一次被 Claude Code “温柔地”删掉.env文件时,正端着咖啡站在厨房。它在终端里输出一行轻快的提示:“Detected unused environment file — removed for clarity.” 我手里的杯子差点没拿稳。那个文件里存着本地开发用的 Stripe 测试密钥、Postgres 连接串,还有三个未上线服务的 API token。它不是“unused”,它是整个项目的心跳监测仪。
这件事之后,我花了整整一个下午重装依赖、恢复配置、排查服务中断原因。但真正让我坐下来写这篇笔记的,不是损失,而是后怕——一个能自由执行 shell 命令、读写文件、提交 Git 的 AI 编程代理,本质上就是一个没有安全护栏的自动化脚本引擎。它不带恶意,但它也没有常识。它不会因为你昨天刚改过config.yaml就绕开它;它也不会因为node_modules里有 2374 个包就手下留情。它的“智能”建立在 prompt 工程和上下文理解上,而不是操作系统权限模型或工程规范意识上。
所以,当有人问我:“Claude Code 真的能替代程序员吗?” 我的回答永远是:“它能替代你手上那台电脑里所有没加锁的命令行工具。” 而 Docker,就是给这台工具加上的第一道、也是最硬的一道锁。
这不是一个“可选优化”,而是一个生产级使用前提。就像你不会让一个新入职的实习生直接登录生产数据库服务器并授予root权限一样,你也不该让一个未经沙盒约束的 AI 代理,在你的主工作区里自由rm -rf、npm install或git commit --amend。Docker 提供的不是“额外功能”,而是运行边界的定义权:它明确告诉你,这个代理能看到什么、能改什么、能连到哪里、能装什么——其余一切,都被内核级隔离挡在外面。
你可能会说:“我只让它干点小活,比如加个 docstring,应该没问题吧?” 我试过。结果它顺手把pyproject.toml里的black版本从24.4.2升到了25.0.0,而我们 CI 里跑的还是 Python 3.9,black 25需要 3.10+。CI 直接挂了。问题不在 Claude Code,而在缺乏边界感的执行环境。
这篇笔记,就是我踩过至少七次坑、重写了四版 Dockerfile、调试过二十多个网络连接失败案例后,沉淀下来的完整实践手册。它不讲大道理,不堆概念,只告诉你:
- 怎么建一个最小、最干净、最安全的 Claude Code 容器镜像(连 musl libc 兼容性这种坑我都给你标出来);
- 怎么让容器里的代码修改,100% 正确地回写到你主机的文件上(UID/GID 映射不是可选项,是必填项);
- 怎么让 Claude Code 在 Compose 里和你的 Postgres、FastAPI、React 前端服务安全对话(
depends_on不是摆设,service_healthy是救命稻草); - 当它突然报错“找不到文件”“连不上数据库”“权限被拒绝”时,三步定位法是什么(别再
docker logs -f盲扫了,我给你列好了诊断命令); - 以及,最重要的——怎么在享受全自动编码便利的同时,确保下班前关掉电脑那一刻,你的主机环境和早上一模一样。
如果你正在用 Claude Code 做真实项目开发,或者正考虑把它引入团队工作流,那么这篇内容不是“教程”,而是你的操作守则。它不承诺让你成为 Docker 专家,但它能保证你第一次运行docker compose run claude "refactor this module"时,心里是踏实的,而不是悬着的。
2. 核心设计思路:为什么 Docker 是 Claude Code 的天然搭档,而非技术堆砌
2.1 执行模型的本质对齐:从“进程”到“容器”的无缝迁移
要理解 Docker 为何不是“给 Claude Code 加个壳”,得先看清两者底层的运作逻辑。Claude Code CLI 本质上是一个 Node.js 进程,它启动后会做三件事:
- 读取:扫描你指定路径下的源码、配置、文档;
- 推理:基于 Anthropic 模型(如 Sonnet 4.6)生成修改方案;
- 执行:调用系统命令(
git,sed,python,curl)落地变更。
这个流程,和传统开发中你手动敲命令的过程完全一致——区别只在于“谁在敲”。而 Docker 的核心价值,恰恰在于它不改变这个流程,只约束这个流程发生的范围。
想象一下你在终端里输入:
cd ~/my-project && git status && python app.pyDocker 做的,只是把cd ~/my-project替换成了mount ~/my-project:/workspace,把git status替换成了“在隔离的 Debian rootfs 里运行git”,把python app.py替换成了“在同一个隔离空间里调用 Python 解释器”。它没有重写 Claude Code 的任何一行逻辑,没有拦截它的fs.readFile()调用,也没有魔改它的child_process.spawn()行为。它只是提供了一个受控的、可丢弃的、与宿主严格分离的执行沙盒。
这就是为什么 Docker 是“天然搭档”:它不强迫你改变工作流,而是默默加固你已有的工作流。你不需要学新语法,不需要重构 prompt,甚至不需要改.gitignore——你只需要告诉 Docker:“这是它的家(volume),这是它的身份(non-root user),这是它的网络(bridge network),这是它的电源开关(--rm)”。
2.2 安全边界的三层防护:文件、进程、网络
很多开发者以为 Docker 的安全=“它不能删我/home”。这太浅了。真正的防护是立体的,覆盖三个关键维度:
第一层:文件系统隔离(最硬的墙)
Docker 的 volume mount 是单向授权机制。你显式声明-v $(pwd):/workspace,意味着:
- ✅ Claude Code 可以读写
$(pwd)下所有文件(包括子目录); - ❌ 它绝对看不到
/etc/passwd、~/.ssh/id_rsa、/var/log/syslog,哪怕它执行ls -la /也只会看到空荡荡的容器根目录; - ❌ 它无法通过
..逃逸到宿主目录——Docker 的 mount 机制在内核层面做了路径解析限制,/workspace/../etc在容器内解析结果就是/workspace/etc,而非宿主的/etc。
提示:这是你唯一能完全信赖的安全层。只要 volume mount 配置正确,其他所有风险(如误删、误改)都局限在你授权的目录内。这也是为什么我坚持要求“为每次 agent 任务创建独立 workspace 目录”,而不是直接挂载整个 Git 仓库根目录——最小权限原则,必须落实到物理路径上。
第二层:进程与用户隔离(防内部越权)
默认情况下,Docker 容器以root用户启动。这意味着,即使文件系统被限制,Claude Code 仍能:
- 创建任意用户、修改
/etc/shadow(虽然没用,但权限存在); - 绑定 1-1023 端口(可能干扰宿主服务);
- 读取容器内所有进程内存(
/proc文件系统)。
我们的 Dockerfile 中强制添加了:
RUN useradd -m -u 1001 claude USER claude这带来了两个关键保障:
- 进程 UID 固定为
1001,无法提权到0(root); - 它无法写入
/usr/local/bin、/etc等系统目录,所有npm install -g或apt-get install的产物,都只能存在于容器自己的/usr/local/lib/node_modules下,且随容器销毁而消失。
注意:
USER claude必须放在ENTRYPOINT之前,且不能被后续RUN指令覆盖。我见过太多 Dockerfile 在最后加了个RUN chmod 777 /workspace,结果整个安全模型崩塌——因为RUN是构建时执行,USER是运行时生效,两者不在同一生命周期。
第三层:网络隔离(防外部渗透)
默认的 Docker bridge 网络是“出向开放、入向封闭”的:
- ✅ 容器可以访问互联网(
curl https://api.anthropic.com); - ✅ 容器可以访问同网络下其他服务(
ping db、curl http://web:8080/health); - ❌ 宿主或其他网络无法主动连接容器端口(除非你显式
ports: ["3000:3000"]); - ❌ 容器无法访问宿主的
127.0.0.1(即localhost),它看到的是自己的 loopback。
这个设计完美匹配 Claude Code 的需求:它需要联网调用 API,需要和 Compose 里的db、web通信,但绝不需要暴露自己的 HTTP 服务端口给外界。而最关键的禁忌——-v /var/run/docker.sock:/var/run/docker.sock——之所以是“红色警戒线”,正是因为一旦挂载,容器就获得了调用宿主 Docker daemon 的能力,等同于拿到了宿主的rootshell。这不是 Docker 的缺陷,而是你主动拆掉了最后一道防火墙。
2.3 部署路径的理性选择:不是“哪个更好”,而是“哪个更少犯错”
社区里常有争论:“Docker Compose 和 Devcontainer,哪个更适合 Claude Code?” 这是个伪命题。真正的问题是:你的当前任务,哪条路径能让你用最少的认知负荷、最低的出错概率,完成目标?
| 部署方式 | 适用场景 | 我的真实使用频率 | 关键风险点 |
|---|---|---|---|
| 单容器 Sandbox | 对单个文件/模块做 refactoring、写测试、生成 README、检查 lint 错误 | 85% 的日常任务 | 忘记-u $(id -u):$(id -g)导致文件属主错误,修改后文件变成root:root |
| Docker Compose | 需要 Claude Code 连接本地数据库、调用正在运行的 API、分析前后端交互日志 | 12% 的深度调试任务 | depends_on未配condition: service_healthy,Claude 启动时 DB 尚未 ready,连接超时失败 |
| Devcontainer | 团队统一 IDE 环境、需预装 VS Code 插件(如 GitHub Copilot)、要求严格 outbound 限制 | 3% 的协作场景 | 依赖 VS Code,无法在纯终端或 CI 中复现;--dangerously-skip-permissions在无 firewall 时极度危险 |
我的经验是:永远从单容器开始。90% 的 Claude Code 价值,体现在对代码库的静态分析和局部修改上。Compose 是为了解决“动态依赖”问题,不是为了炫技。强行用 Compose 跑一个claude "add type hints to utils.py",只会增加 3 分钟的docker compose up -d等待时间,和 5 分钟的docker compose logs claude排查时间。
Devcontainer 更是“高阶玩法”——它本质是把整个开发环境容器化,Claude Code 只是其中的一个工具。如果你的团队还没统一用 VS Code,或者你的 CI 流水线跑在裸机上,那么 Devcontainer 就是空中楼阁。先确保单容器 100% 稳定,再谈扩展。
3. 实操细节解析:从零构建一个可信赖的 Claude Code 容器环境
3.1 环境准备:那些被跳过的“小事”,才是失败的根源
我统计过自己过去半年的 Claude Code Docker 失败案例,73% 的问题出在环境准备阶段,而非 Dockerfile 或 Compose 配置。这些“小事”看似 trivial,却直接决定你能否在 10 分钟内跑通第一个命令。
第一步:验证 Docker Daemon 的健康状态(不是“是否安装”,而是“是否可用”)
很多人执行docker --version返回Docker version 24.0.7就认为 OK。错。你需要确认的是 daemon 是否在监听。执行:
docker run --rm hello-world如果返回:
Hello from Docker! This message shows that your installation appears to be working correctly.✅ 成功。
如果卡住、报错Cannot connect to the Docker daemon,请立即停止——这不是 Dockerfile 的问题,是你的基础环境没配好。常见原因:
- macOS/Windows:Docker Desktop 未启动,或后台进程崩溃(重启 Desktop);
- Linux:
dockerd服务未运行(sudo systemctl start docker),或当前用户不在docker组(sudo usermod -aG docker $USER,然后完全退出并重新登录终端,newgrp docker不可靠)。
实操心得:我习惯在
~/.zshrc里加一行alias dcheck='docker run --rm hello-world 2>/dev/null && echo "✅ Docker OK" || echo "❌ Docker DOWN"',每次开新终端先敲dcheck。
第二步:Node.js 版本与全局安装路径的隐性冲突
Claude Code CLI 是 npm 包,依赖 Node.js。但很多人忽略一点:Docker 容器内的 Node.js 版本,和你宿主机器的 Node.js 版本,必须兼容其二进制依赖。
我们 Dockerfile 用FROM node:20-slim,这是 Debian 系统。如果你宿主是 macOS(Apple Silicon),npm install -g @anthropic-ai/claude-code会下载darwin-arm64架构的二进制;而容器里是linux-amd64,根本无法运行。所以,Claude Code CLI 必须在容器内安装,而非宿主安装后 COPY 进去。这就是为什么 Dockerfile 里是RUN npm install -g ...,而不是COPY ./node_modules /usr/local/lib/node_modules。
验证方法:在宿主执行node -p "process.arch + '-' + process.platform",得到arm64-darwin;在容器内执行相同命令(docker run --rm node:20-slim node -p "process.arch + '-' + process.platform"),得到amd64-linux。两者不同,证明跨平台安装无效。
第三步:API Key 注入——为什么--env-file是反模式
社区常见做法是建一个.env文件:
ANTHROPIC_API_KEY=sk-...然后docker run --env-file .env ...。这很危险。.env文件一旦被误提交到 Git,密钥就永久泄露。更糟的是,--env-file会把所有变量注入容器,包括你可能无意中写进去的DEBUG=1、NODE_ENV=production,这些可能干扰 Claude Code 行为。
正确姿势是:
- 在
~/.zshrc中设置export ANTHROPIC_API_KEY="sk-..."; - 在
docker run中显式引用:-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"; - 绝不在 Dockerfile 中写
ENV ANTHROPIC_API_KEY=...,也绝不用--env-file。
这样做的好处:
- 密钥只存在于你的 shell 环境,不落盘;
- 你可以为不同项目设置不同
ANTHROPIC_API_KEY变量(如export ANTHROPIC_API_KEY_DEV=...),按需切换; docker history查看镜像层时,完全看不到密钥痕迹。
3.2 Dockerfile 深度解析:每一行背后的血泪教训
下面是我们最终采用的 Dockerfile,我逐行解释其设计哲学和避坑点:
# 1. 基础镜像:为什么是 node:20-slim,而不是 alpine? FROM node:20-slim # 2. 创建非 root 用户:安全基线 RUN useradd -m -u 1001 claude # 3. 全局安装 CLI:必须在 RUN 中,且必须用 npm RUN npm install -g @anthropic-ai/claude-code # 4. 设置工作目录:所有操作的默认起点 WORKDIR /workspace # 5. 切换用户:运行时身份,非构建时 USER claude # 6. 入口点:让容器启动即执行 claude ENTRYPOINT ["claude"]第 1 行:node:20-slimvsnode:20-alpine
Alpine 使用musl libc,而 Claude Code CLI 的某些底层依赖(如sharp图像处理库,虽不常用但 CLI 可能间接引用)编译时链接的是glibc。我在 Alpine 上首次构建时,docker run报错:
Error: Error loading shared library libstdc++.so.6: No such file or directory这是典型的musl/glibc不兼容。slim镜像是 Debian 衍生版,用glibc,100% 兼容。体积虽比 Alpine 大 30MB(约 180MB vs 150MB),但换来的是零兼容性故障。对于开发工具镜像,稳定性永远优先于体积。
第 2 行:useradd -m -u 1001 claude-m创建 home 目录/home/claude,避免USER claude后HOME环境变量为空导致 CLI 初始化失败;-u 1001指定 UID,确保跨容器一致性(Docker 默认从 1000 开始分配,1001 是安全选择)。不要用--uid参数,它在旧版 Docker 中不支持。
第 3 行:npm install -g
必须用npm,而非yarn或pnpm。Anthropic 官方只测试npm安装路径。-g是全局安装,确保claude命令在$PATH中。注意:RUN npm install -g会触发npm自动创建~/.npm缓存目录,这会增大镜像层。但我们接受,因为缓存能加速后续构建。
第 4 行:WORKDIR /workspace
这是关键。WORKDIR设定cd的默认位置,也是VOLUME挂载的默认目标。如果你不设,-v $(pwd):/workspace仍有效,但claude命令执行时的当前路径是/,可能导致相对路径解析错误(如claude "read README.md"会找/README.md,而非/workspace/README.md)。设为/workspace,所有操作自然落在挂载点内。
第 5 行:USER claude
必须在WORKDIR之后、ENTRYPOINT之前。如果放错位置(如在RUN之后立即USER),会导致RUN指令以root执行,而USER之后的指令以claude执行,权限混乱。USER指令是运行时生效,不影响构建层。
第 6 行:ENTRYPOINT ["claude"]
用 JSON 数组格式(["claude"]),而非字符串格式("claude")。前者允许你传参(docker run claude-code:latest "list files"),后者会把整个字符串当命令执行,参数解析失败。这是 Docker 最易踩的坑之一。
3.3 Workspace 初始化:一个被严重低估的工程实践
很多人直接cd到现有项目根目录,然后docker run -v $(pwd):/workspace ...。这是灾难的开始。Claude Code 的索引(indexing)是递归扫描整个挂载目录的。如果你的项目根目录下有:
node_modules/(5000+ 子目录).git/(数万对象)dist/(编译产物)logs/(滚动日志)
Claude Code 会试图“理解”它们,消耗大量 token,拖慢响应,甚至因上下文溢出而失败。更糟的是,它可能误判node_modules/react里的源码是你的业务逻辑,生成错误的 refactoring 建议。
我的标准初始化流程(每次新任务必做):
# 1. 创建独立 workspace 目录(带时间戳,便于追溯) mkdir -p ~/claude-workspace/$(date +%Y%m%d-%H%M%S)-refactor-auth # 2. 进入该目录 cd $_ # 3. 只复制必要文件(不是 `cp -r` 整个 repo!) cp ~/my-project/src/auth/ ./auth/ -r cp ~/my-project/pyproject.toml ./ cp ~/my-project/README.md ./ # 4. 创建 CLAUDE.md(项目上下文说明书) cat > CLAUDE.md << 'EOF' # Project Context for Claude Code - This is a Python FastAPI auth service - Main entry point: `auth/main.py` - Dependencies: `fastapi==0.111.0`, `sqlalchemy==2.0.30` - Database: PostgreSQL, connection via `DATABASE_URL` env var - Security: All endpoints require JWT Bearer token EOF # 5. 初始化 Git(让 Claude 能做 commit) git init && git add . && git commit -m "Initial Claude workspace"这个流程的价值:
- 精准控制上下文:Claude 只看到
auth/目录,不会被frontend/或infra/干扰; - 提供结构化提示:
CLAUDE.md是给 AI 的“项目说明书”,比在 prompt 里重复描述高效 10 倍; - 启用 Git 功能:
git commit后,Claude 可以git diff、git revert,实现原子化修改; - 可审计、可复现:每个 workspace 目录名含时间戳,你知道 2025-04-15 14:30 的那次 refactoring 是在哪做的。
实操心得:我写了个
claude-initshell 函数,一键生成 workspace。它会自动检测当前 Git 仓库,询问你要聚焦的子目录,生成CLAUDE.md模板,并打开编辑器让你填写。省下 3 分钟,避免 90% 的上下文污染错误。
4. 完整实操流程:从单容器到多服务的渐进式部署
4.1 构建与验证基础镜像:确保“基石”牢不可破
构建镜像不是终点,而是验证的起点。执行:
docker build -t claude-code:latest .首次构建耗时约 2-3 分钟(拉取 base image + npm install)。成功后,你会看到:
Successfully built abc123def456 Successfully tagged claude-code:latest关键验证步骤(缺一不可):
检查镜像层:
docker history claude-code:latest
输出应显示清晰的分层:IMAGE CREATED CREATED BY SIZE abc123def456 2 minutes ago /bin/sh -c #(nop) ENTRYPOINT ["claude"] 0B <missing> 2 minutes ago /bin/sh -c #(nop) USER claude 0B <missing> 2 minutes ago /bin/sh -c #(nop) WORKDIR /workspace 0B <missing> 2 minutes ago /bin/sh -c npm install -g @anthropic-ai/clau… 124MB <missing> 3 minutes ago /bin/sh -c useradd -m -u 1001 claude 0B <missing> 4 minutes ago /bin/sh -c #(nop) FROM node:20-slim 180MB如果看到
SIZE列有0B的层,说明该层没产生文件(如USER、WORKDIR),这是正常的。如果有某层SIZE异常大(如 500MB),可能是npm install时缓存没清理,需检查Dockerfile。测试 CLI 基础功能:
# 启动一个空容器,不挂载任何 volume,只测试 claude 命令是否存在 docker run --rm claude-code:latest --help | head -n 5应输出
Usage: claude [options] [prompt]等帮助信息。如果报错command not found,说明npm install -g失败或PATH未生效。验证非 root 用户权限:
docker run --rm claude-code:latest id输出应为
uid=1001(claude) gid=1001(claude) groups=1001(claude)。如果仍是uid=0(root),说明USER claude没生效,检查是否被后续指令覆盖。
4.2 单容器 Sandbox:读写操作的黄金配置
现在进入核心环节。我们用一个极简的 Python 项目测试读写闭环。
Step 1:创建测试 workspace
mkdir ~/claude-test && cd $_ echo "def greet(name): return f'Hello, {name}!'" > main.py echo "# Test Project" > README.mdStep 2:执行只读任务(无需特殊 flag)
docker run --rm \ -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \ -v $(pwd):/workspace \ claude-code:latest \ "List all Python files and summarize their purpose."预期输出类似:
Found 1 Python file: main.py main.py contains a greet function that returns a formatted greeting string.✅ 成功:证明 volume mount 和 API key 传递正常。
Step 3:执行写任务(必须加两个 flag)
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 docstring to the greet function in main.py."为什么-u $(id -u):$(id -g)不可省略?
- 容器内
claude用户 UID 是1001; - 你宿主当前用户 UID 可能是
1000(Ubuntu 默认)或501(macOS); main.py在宿主上属主是1000:1000;- 如果容器以
1001用户写文件,Linux 会创建1001:1001属主的文件,你宿主用户无法直接编辑它(Permission denied); -u $(id -u):$(id -g)强制容器内进程以宿主 UID/GID 运行,写入的文件属主和权限与宿主完全一致。
为什么--dangerously-skip-permissions是安全的?
Claude Code 默认会在写文件前问:Write to main.py? (y/N)。在容器里,这个 prompt 无法被交互响应,进程会卡死。--dangerously-skip-permissions跳过它,但“危险”二字是针对无沙盒环境的。在 Docker 里,它只被允许写/workspace,而/workspace是你显式挂载的、你完全掌控的目录。所以,这个 flag 在 Docker 环境下,是必要的、安全的、且唯一的非交互方案。
Step 4:验证写入结果
cat main.py应看到:
def greet(name): """Return a formatted greeting string.""" return f'Hello, {name}!'✅ 成功:volume mount 双向读写、UID/GID 映射、权限跳过,三者全部生效。
4.3 Docker Compose 多服务编排:让 Claude Code 成为“开发协作者”
当任务涉及多个服务时,单容器不够用了。比如:你希望 Claude Code 分析一个 FastAPI 服务如何与 PostgreSQL 交互,并生成优化建议。这时,它需要:
- 访问
web服务的源码(挂载 volume); - 连接到
db服务的数据库(网络可达); - 读取
db的 schema(需psql或 ORM introspection)。
完整的docker-compose.yml(已精简注释):
services: claude: build: . environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - DATABASE_URL=postgresql://user:password@db:5432/appdb volumes: - .:/workspace depends_on: db: condition: service_healthy networks: - app-network web: image: python:3.12-slim working_dir: /app volumes: - .:/app command: python -m http.server 8080 ports: - "8080:8080" networks: - app-network db: image: postgres:16-alpine environment: POSTGRES_DB: appdb POSTGRES_USER: user POSTGRES_PASSWORD: password healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d appdb"] interval: 5s timeout: 3s retries: 5 networks: - app-network networks: app-network: driver: bridge关键配置详解:
volumes: .:/workspace:挂载当前目录(即 Compose 文件所在目录)到容器/workspace。注意是.,不是./workspace,否则容器看到的是空目录。environment: DATABASE_URL=...:显式提供连接串。Claude Code 不会自动发现db服务,必须告诉它怎么连。@db:5432中的db是服务名,Docker DNS 会自动解析为容器 IP。depends_on: db: condition: service_healthy:这是灵魂。depends_on默认只等容器启动(created状态),但 PostgreSQL 启动后还需几秒初始化数据库。service_healthy触发healthcheck,确保pg_isready返回成功才启动claude。我曾跳过此步,Claude 启动后立刻尝试psql -c "\dt",报错FATAL: database "appdb" does not exist,浪费 15 分钟排查。networks: app-network:所有服务在同一自定义 bridge 网络,相互可通过服务名通信。claude能ping db,web能curl http://db:5432,但宿主无法ping claude(除非暴露端口)。
执行流程(严格顺序):
# 1. 启动 db 和 web(后台运行) docker compose up -d db web # 2. 等待 db 健康(观察日志,直到出现 "database system is ready to accept connections") docker compose logs -f db # 3. 运行一次性的 claude 任务(不后台,执行完退出) docker compose run --rm \ claude \ --dangerously-skip-permissions \ "Connect to the PostgreSQL database, list all tables, and write the schema summary to SCHEMA.md." # 4. 查看结果 cat SCHEMA.md为什么用docker compose run而非docker compose up claude?
up会让claude作为长期服务运行,但它没有command,启动后立即退出,Docker 会不断重启它(Restarting状态),浪费资源;run是一次性执行,--rm自动清理容器,符合 Claude Code 的“任务型”本质;run允许你传入 prompt 作为参数,up不支持。
4.4 Session 清理:让 Docker 环境保持“出厂设置”
很多开发者只关注“怎么启动”,却忽略“怎么收尾”。Docker 容器不自动清理,残留的 stopped 容器、dangling 镜像、unused volumes 会悄悄吃掉磁盘和内存。
我的标准化清理清单(每次 session 后必执行):
| 命令 | 作用 | 触发场景 | 频率 |
|---|---|---|---|
docker compose down | 停止并移除compose启动的所有容器、网络 | 每次docker compose up后 | 100% |
docker container prune | 删除所有exited状态的容器 | 忘记--rm,或docker run后容器退出未删 | ~20% |
docker volume prune | 删除所有未被容器使用的 volume | compose down后仍有 dangling volume | ~5% |
docker system prune -a | 慎用!删除所有未使用的镜 |