Moby 开发环境搭建实战:make shell 进入 Docker-in-Docker 开发容器并验证代码迭代流程
本篇技术指南完整覆盖 set-up-dev-env.md 文档的核心流程:清理宿主机残留的 Docker 工件、用仓库根目录的 Dockerfile 构建并启动 Moby 开发容器、在容器内编译 dockerd 并启动守护进程验证 hello-world,以及完成一次"改代码 → 重新构建 → 验证生效"的最小迭代。读完后你可以按 Moby 引擎核心团队的方式搭建本地开发环境,并掌握 make shell、hack/make.sh、DOCKER_CLI_PATH 等关键机制的底层实现。
开发环境概览:一个"开发环境本身也是 Docker 容器"
Moby 的开发环境本身就是一个 Docker 容器:仓库根目录的 Dockerfile 定义了该环境的全部依赖——系统库与二进制、Go 工具链、Go 依赖等。开发流程是:
- 使用
moby/moby仓库及其Dockerfile构建一个开发镜像; - 运行该镜像得到一个开发容器(Docker-in-Docker);
- 在容器内部编写、编译、运行和测试引擎代码。
文档的前置假设是:你已按 set up Git for contributing 完成了 moby/moby 的 fork,并创建了名为 dry-run-test 的特性分支。后续步骤都基于这个 fork 的该分支进行。
结合当前仓库源码,这个开发镜像的构建细节值得展开:
- 基础镜像与 Go 工具链:Dockerfile 以
golang:1.26.8-bookworm为基底(ARG GO_VERSION=1.26.8),并通过tonistiigi/xx提供交叉编译能力。 - 固定版本的配套组件:开发容器内置了与引擎强绑定的工具链,包括
containerd v2.3.4、runc v1.5.1、docker CLI v29.7.1(来自 docker/cli 项目)、buildx 0.36.1、compose v5.5.0、registry 3.1.1(用于集成测试)、delve v1.26.3(调试器)、golangci-lint、gotestsum等。 - 两个关键 stage:
dev-base:安装开发所需的系统包(iptables、nftables、libseccomp-dev、fuse-overlayfs等),并把各构建 stage 的二进制(runc、containerd、rootlesskit、tini等)复制到/usr/local/bin/;dev:在dev-base基础上执行COPY --link . .,即把仓库源码完整复制进镜像。当BIND_DIR=.(本地直跑)时,Makefile 改用--target=dev-base,依赖 bind mount 提供源码。
ENTRYPOINT ["hack/dind"]:所有命令都经过 hack/dind 包装,该脚本在 privileged 容器内挂载securityfs(让 AppArmor 可用)、按需挂载tmpfs /tmp、在 cgroup v2 上启用嵌套 cgroup 控制器、并把根文件系统改为 shared 挂载传播,使容器内部环境尽可能接近一个真实 Linux 主机。这就是"Moby inception"(容器里跑 Docker)能够工作的基础。
Task 1. 清理镜像与容器
Moby 开发者使用最新稳定版 Docker,并保持宿主机干净(清理不是硬性要求,但是良好实践)。
1. 清理容器
先确认宿主机没有需要保留的容器:
$ docker ps -a
正常干净的输出只有表头:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
快速移除所有旧容器的现代做法:
$ docker system prune -a
旧版本 Docker Engine 可用等价命令:
$ docker rm $(docker ps -a -q)
该命令先用 docker ps 列出所有容器(-a 含已停止的)并以数字 ID 输出(-q),再由 docker rm 删除列表。如果有"运行中但不需要"的容器,先用 docker stop 停止再 docker rm。
2. 清理 dangling 镜像
$ docker images
干净宿主机的输出只有表头:
REPOSITORY TAG IMAGE ID CREATED SIZE
可能存在的 dangling(悬空)镜像指"没有被任何运行中容器使用、且不是系统中其他镜像祖先"的镜像。快速清除:
$ docker rmi -f $(docker images -q -a -f dangling=true)
docker images 用 -a 列全部镜像、-q 只输出 ID、-f dangling=true 过滤出悬空镜像,最后 docker rmi -f 强制删除。若提示 "rmi" requires a minimum of 1 argument,说明没有悬空镜像。删除单个镜像则用 docker rmi ID。
Task 2. 启动开发容器
这一步构建开发环境镜像并运行容器,由仓库的 Makefile 自动完成。首次构建可能耗时超过 15 分钟(需要下载依赖与构建各 stage)。
-
打开终端。对 Docker Toolbox 用户,先用
docker-machine status your_vm_name确认 VM 在运行,并eval "$(docker-machine env your_vm_name)"初始化 shell 环境;使用 Docker Desktop(Mac/Windows)则无需 Docker Machine。 -
进入 moby-fork 仓库根目录:
$ cd ~/repos/moby-fork -
确认在
dry-run-test分支上:$ git checkout dry-run-test若提示分支不存在,用
git checkout -b dry-run-test同时创建并切换。 -
用
make构建开发镜像并启动容器:$ make shellmake shell依赖build目标,从源码看(Makefile)其执行链路是:build: validate-bind-dir bundles $(BUILD_CMD) $(BUILD_OPTS) $(shell_target) --load -t "$(DOCKER_IMAGE)" . .PHONY: shell shell: build ## start a shell inside the build env $(DOCKER_RUN_DOCKER) bash其中
$(BUILD_CMD)即docker buildx build,镜像名固定为docker-dev(DOCKER_IMAGE := docker-dev)。构建成功后会打印类似:Successfully built 3d872560918e docker run --rm -i --privileged -e BUILDFLAGS -e KEEPBUNDLE -e DOCKER_DEBUG ... #此时你的提示符已进入容器内的 Bash。注意
make shell的容器启动参数来自 Makefile 的DOCKER_FLAGS:--rm --privileged、透传DOCKER_ENVS中列出的全部环境变量(BUILDFLAGS、DOCKER_LDFLAGS、DOCKER_CLI_PATH、TESTFLAGS等)、并按BIND_DIR决定挂载。Note:
make shell默认创建打docker-dev:latest标签的镜像,不会自动用当前分支名打标签。一些旧文档提到的"分支专属标签"行为已不再使用。IDE 方式(devcontainer):也可以在支持 devcontainer 的 IDE(VSCode、GoLand 等)中使用仓库提供的 .devcontainer/devcontainer.json。其配置为:以仓库根目录为构建上下文、用 Dockerfile 的
devcontainerstage(在dev-base基础上额外安装gopls)、工作区固定为/usr/src/moby、通过workspaceMount以 bind 方式把本地目录映射进容器(consistency=cached)、以root用户运行并附加--privileged。 -
列出
/usr/src/moby目录,应能看到镜像中的源码: -
在容器内构建
dockerd二进制:# hack/make.sh binary Removing bundles/ ---> Making bundle: binary (in bundles/binary) Building bundles/binary-daemon/dockerd (linux/amd64)... Created binary: bundles/binary-daemon/dockerd Building bundles/binary-daemon/docker-proxy (linux/amd64)... Created binary:bundles/binary-daemon/docker-proxyhack/make.sh 会读取仓库根目录的
VERSION文件作为二进制版本,并把 git HEAD 写入版本信息;若仓库有未提交改动,版本会追加-unsupported后缀(见 hack/make.sh)。 -
安装二进制到容器的
/usr/local/bin/:# make install对应 Makefile 的
install目标,即KEEPBUNDLE=1 hack/make.sh install-binary。从 hack/make/install-binary 可见,安装的不止dockerd,还包括runc、containerd、ctr、containerd-shim-runc-v2、docker-proxy、docker-init、rootlesskit及 rootless 启动脚本——即引擎运行所需的一整套用户态组件。 -
后台启动引擎守护进程:
# dockerd -D & ...output snipped... DEBU[0001] Registering POST, /networks/{id:.*}/connect DEBU[0001] Registering POST, /networks/{id:.*}/disconnect DEBU[0001] Registering DELETE, /networks/{id:.*} INFO[0001] API listen on /var/run/docker.sock DEBU[0003] containerd connection state change: READY-D以 debug 模式启动,&放入后台;调试代码开发时这些选项非常有用。启动后需要按一次return才能拿回 shell 提示符。也可以用一条命令自动化 build、install、run 三步(完成后按
ctrl-z挂起,再执行bg 1让守护进程回到后台):hack/make.sh binary install-binary run其中
run对应的 hack/make/run 会以--debug --host=tcp://0.0.0.0:2375 --host=unix:///var/run/docker.sock --tls=false等参数启动dockerd,并支持通过DELVE_PORT用 delve headless 方式拉起dockerd以便远程调试、通过DOCKER_ROOTLESS切换为 rootless 用户unprivilegeduser运行。 -
容器内检查 Docker 版本:
# docker version会看到 client 与 server 版本"分裂"的现象(示例输出中 client 是
17.06.0-ce,server 是dev)。这是因为 Docker CLI 组件(提供docker命令)早已从 Moby 项目中拆分出去,独立维护;Moby 项目对集成测试默认使用固定版本的dockerCLI。你启动容器时可能看到如下提示:Makefile:123: The docker client CLI has moved to github.com/docker/cli. For a dev-test cycle involving the CLI, run: DOCKER_CLI_PATH=/host/path/to/cli/binary make shell then change the cli and compile into a binary at the same location.设置
DOCKER_CLI_PATH即可向开发容器注入一个更新的dockerCLI,供测试与integration-cli测试执行使用:make DOCKER_CLI_PATH=~/go/src/github.com/docker/cli/build/docker shell ... # which docker /usr/local/cli/docker # docker --version Docker version 29.0.0-dev, build 09cd4ea26c该 CLI 必须来自 docker/cli 项目且是 Linux 二进制。从 Makefile 看其生效机制是 DOCKER_MOUNT_CLI:当
DOCKER_CLI_PATH非空时,把该二进制所在目录挂载到容器内/usr/local/cli,而 Dockerfile 已将PATH=/usr/local/cli:$PATH置于最前,因此容器内docker命令优先解析到它。容器内运行的引擎版本是当前分支的开发版本,反映仓库根目录
VERSION文件的值。 -
运行
hello-world镜像:# docker run hello-world -
列出刚下载的镜像:
# docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest c54a2cc56cbb 3 months ago 1.85 kB -
在宿主机另开一个终端。
-
列出正在运行的开发容器:
ubuntu@ubuntu1404:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a8b2885ab900 docker-dev "hack/dind bash" 43 minutes ago Up 43 minutes hungry_payne注意
COMMAND显示为hack/dind bash——再次印证开发容器的入口就是 hack/dind 包装脚本。
Task 3. 做一次代码改动
至此你已完成"Moby inception"全流程:fork 并克隆引擎仓库、创建特性分支、从分支创建并启动开发容器、在容器内编译二进制、用新二进制启动 docker 守护进程、再在开发容器内用 docker 客户端运行 hello-world。make shell 把本地仓库源码挂载/复制到了容器内,接下来开始真正的迭代开发。
下面用一个简单改动(编辑 attach/引擎帮助文本)演示"改动在容器中即时反映"。
-
若没有,打开宿主机的一个终端。
-
确认在 moby-fork 仓库中:
$ pwd /Users/mary/go/src/github.com/moxiegirl/moby-fork -
编辑命令帮助信息,例如将第 28 行的:
Short: "A self-sufficient runtime for containers.",改为:
Short: "A self-sufficient and really fun runtime for containers.", -
保存并关闭文件。
-
切回正在运行的开发容器 shell。
-
在容器内用
hack/make.sh binary重新构建二进制。 -
若 Docker 守护进程在运行,先停止它。
-
把二进制复制到容器的
/usr/local/bin/:hack/make.sh binary install-binary -
在容器内运行
dockerd --help查看改动:# dockerd --help Usage: dockerd COMMAND A self-sufficient and really fun runtime for containers. Options: ...
这就是修改 Moby 引擎代码库的基本工作流:在特性分支上改代码 → 在开发容器内更新二进制 → 验证改动。更大的改动会反复迭代这个循环多次。
如果想免除手工循环,仓库还提供了一个"开发模式":Makefile 的 dev 目标运行 hack/dev.sh,它在无限循环中自动执行 hack/make.sh binary → install-binary → dockerd --debug(构建失败则等 5 秒重试)。这意味着你在宿主机的编辑器里保存代码后,开发容器会自动重新构建并以新二进制重启引擎,是比"手动三步"更顺手的迭代方式。
下一步
恭喜你完成了 Docker inception,并初步体验了 Moby 的开发流程:搭建好开发环境、验证了贡献所需的核心环节。在真正开始贡献之前,还需要再了解开发流程中的一块拼图——测试框架,参见 test.md。
适用前提与限制:上述流程假设宿主机已安装可用的 Docker(含 buildx),开发容器需要
--privileged权限与 cgroup 支持;make shell首次构建耗时较长;DOCKER_CLI_PATH注入的 CLI 必须是 Linux 二进制。相关机制(环境变量透传、挂载、构建 stage)均可在仓库根目录的 Makefile 与 Dockerfile 中查证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
