深入解析 Docker Remote API v1.6:从 Moby 早期 REST 接口到流式多路复用协议的设计源头
本文基于 Moby 仓库中的历史 API 文档 Remote API v1.6 展开,完整还原了 Docker 0.5 时代 Remote API 的端点全貌——容器、镜像、构建、认证与事件流——并深入剖析其最核心的技术资产:attach 流式多路复用协议(8 字节帧头 + big-endian 长度)。读完后你将理解现代 Docker Engine API 的设计源头,并能看懂当前仓库中 stdcopy 包 里一脉相承的实现代码。
1. v1.6 的历史定位与 API 总体特征
v1.6 是 Docker 早期(0.5.x 时代,2013 年)的 Remote API 版本。按照文档自身的描述,这一版 API 有三个核心特征:
- Remote API 取代了 rcli(早期的本地客户端机制),所有交互都通过 HTTP/REST 进行;
- 默认监听在
unix:///var/run/docker.sock,但可以把 daemon 绑定到其他 host/port 或任意 Unix socket; - 整体是 REST 风格,但对
attach、pull等复杂命令采用 HTTP hijacking——直接劫持 HTTP 连接来双向传输stdin、stdout、stderr。
适用前提说明:当前 Moby 仓库中的 daemon 已不再支持 v1.6。从 daemon/config/config.go 可以看到,当前版本支持的最低 API 版本为
1.24(MinAPIVersion),默认最低为1.40,最高为1.56(MaxAPIVersion)。因此本文对 v1.6 的讨论属于历史版本研读:它解释了端点语义与协议格式如何定型,而这些语义至今仍在现代 API 中延续。
当前仓库中,客户端请求经过 版本协商中间件 后才会路由到具体后端:该中间件在 version.go 中通过 Api-Version 响应头告知默认版本,并按 URL 路径中的版本号决定拒绝或放行。这与 v1.6 时代的"一个 daemon 一个 API 形态"不同,是现代多版本共存的基础设施。
2. 容器端点(Containers)
v1.6 文档第 2.1 节定义了完整的容器生命周期端点。下面按"查询 → 创建 → 生命周期控制 → 流式交互 → 数据操作"的顺序逐一还原。
2.1 列出容器 GET /containers/json
示例请求:
GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1
示例响应(200 OK,Content-Type: application/json):
[
{
"Id": "8dfafdbc3a40",
"Image": "base:latest",
"Command": "echo 1",
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
"SizeRw": 12288,
"SizeRootFs": 0
},
{
"Id": "9cd87474be90",
"Image": "base:latest",
"Command": "echo 222222",
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [],
"SizeRw": 12288,
"SizeRootFs": 0
}
]
查询参数:
| 参数 | 说明 |
|---|---|
all |
1/True/true 或 0/False/false,是否显示全部容器;默认只显示运行中的容器(默认 false) |
limit |
只显示最近创建的 limit 个容器,包含未运行的 |
since |
只显示该 Id 之后创建的容器,包含未运行的 |
before |
只显示该 Id 之前创建的容器,包含未运行的 |
size |
1/True/true 或 0/False/false,是否附带容器大小(SizeRw/SizeRootFs 字段) |
状态码:200 成功;400 参数错误;500 服务端错误。
这一路由在今天的 容器路由注册表 中依然是 GET /containers/json,说明端点形态在十余年间保持稳定。
2.2 创建容器 POST /containers/create
示例请求(Content-Type: application/json,请求体即容器配置 config):
{
"Hostname":"",
"User":"",
"Memory":0,
"MemorySwap":0,
"AttachStdin":false,
"AttachStdout":true,
"AttachStderr":true,
"ExposedPorts":{},
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":null,
"Cmd":["date"],
"Dns":null,
"Image":"base",
"Volumes":{},
"VolumesFrom":"",
"WorkingDir":""
}
示例响应(201 Created):
{
"Id":"e90e34656806",
"Warnings":[]
}
- 请求体参数
config是容器的完整配置(Hostname、User、Memory、MemorySwap、Tty、Cmd、Image、Volumes、WorkingDir等); - 查询参数
name用于指定容器名称; - 状态码:
201成功;404镜像/容器不存在;406无法附加(容器未运行);500服务端错误。
两步式实战示例(原文档给出的经典用法:先暴露私有端口,再启动时映射到宿主机公开端口):
第一步,创建时通过 ExposedPorts 声明 22/tcp:
POST /containers/create HTTP/1.1
Content-Type: application/json
{
"Cmd":["/usr/sbin/sshd","-D"],
"Image":"image-with-sshd",
"ExposedPorts":{"22/tcp":{}}
}
返回 201,得到容器 Id(如 e90e34656806)。第二步,用该 Id 启动并绑定端口:
POST /containers/e90e34656806/start HTTP/1.1
Content-Type: application/json
{
"PortBindings": { "22/tcp": [{ "HostPort": "11022" }]}
}
返回 204 No Content,此后即可通过宿主机的 11022 端口 SSH 进入容器。这个"create 与 start 分离、端口绑定放在 start 的 hostConfig 中"的模式,就是今天 docker run -p 11022:22 底层调用链的雏形。
2.3 查看容器 GET /containers/(id)/json
返回容器的低层信息。示例请求 GET /containers/4fa6e0f0c678/json,响应结构包含三大块(原文档示例节选):
{
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
"Created": "2013-05-07T14:51:42.041847+02:00",
"Path": "date",
"Args": [],
"Config": {
"Hostname": "4fa6e0f0c678",
"Tty": false,
"Env": null,
"Cmd": ["date"],
"Image": "base",
"Volumes": {},
"WorkingDir": ""
},
"State": {
"Running": false,
"Pid": 0,
"ExitCode": 0,
"StartedAt": "2013-05-07T14:51:42.087658+02:00",
"Ghost": false
},
"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
"NetworkSettings": {
"IpAddress": "",
"IpPrefixLen": 0,
"Gateway": "",
"Bridge": "",
"PortMapping": null
},
"ResolvConfPath": "/etc/resolv.conf",
"Volumes": {}
}
状态码:200/404(无此容器)/500。
2.4 查看容器内进程与文件系统变更
GET /containers/(id)/top:列出容器内进程。响应是Titles(列名:USER、PID、%CPU、%MEM、VSZ、RSS、TTY、STAT、START、TIME、COMMAND)加Processes(二维数组)的结构;查询参数ps_args可传入ps参数(如aux)。状态码200/404/500。GET /containers/(id)/changes:检查容器文件系统的变更。响应为{"Path": "/dev/kmsg", "Kind": 1}形式的列表,Kind用数字编码变更类型(如0表示未变更、1表示修改/新增,对应今天的ChangeTypes语义)。状态码200/404/500。
2.5 生命周期控制:start / stop / restart / kill
这四个端点共享一套"幂等、短响应"的设计:
| 端点 | 查询参数 | 成功状态码 |
|---|---|---|
POST /containers/(id)/start |
请求体 hostConfig(可选):Binds、LxcConf、ContainerIDFile、Privileged、PortBindings、Links、PublishAllPorts |
204 |
POST /containers/(id)/stop |
t – 强制杀死前等待的秒数 |
204 |
POST /containers/(id)/restart |
t – 强制杀死前等待的秒数 |
204 |
POST /containers/(id)/kill |
signal – 要发送的信号(整数);未指定时默认为 SIGKILL 并等待容器退出 |
204 |
start 的示例请求体展示了 v1.6 时代 host 级配置仍随启动下发:
{
"Binds":["/tmp:/tmp"],
"LxcConf":[{"Key":"lxc.utsname","Value":"docker"}],
"ContainerIDFile": "",
"Privileged": false,
"PortBindings": {"22/tcp": [{HostIp:"", HostPort:""}]},
"Links": [],
"PublishAllPorts": false
}
注意 LxcConf 这一字段——它记录了 Docker 尚绑定 LXC 运行时的历史痕迹。上述端点的失败状态码统一为 404(无此容器)与 500(服务端错误)。
2.6 附加到容器 POST /containers/(id)/attach
示例请求:
POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1
示例响应:
HTTP/1.1 200 OK
Content-Type: application/vnd.docker.raw-stream
{{ STREAM }}
查询参数(全部为 1/True/true 或 0/False/false 布尔风格,默认 false):
logs– 是否返回历史日志;stream– 是否返回实时流;stdin– 若stream=true,附加 stdin;stdout– 若logs=true返回 stdout 日志,若stream=true附加 stdout;stderr– 语义同stdout,作用于 stderr。
状态码:200/400(参数错误)/404/500。
此外 v1.6 还提供 WebSocket 变体 GET /containers/(id)/attach/ws,按 RFC 6455 完成 WebSocket 握手后传输同样的流,查询参数与 POST 版一致。这一 WebSocket attach 路由在当前仓库中依然存在,见 container.go 中的 GET /containers/{name:.*}/attach/ws 路由注册。
2.7 数据操作:wait / remove / export / copy
POST /containers/(id)/wait:阻塞直到容器停止,然后返回退出码。示例响应:{"StatusCode": 0}。状态码200/404/500。DELETE /containers/(id):从文件系统删除容器。查询参数v(1/True/true 或 0/False/false,默认 false)控制是否连带删除关联卷。状态码204/400/404/500。GET /containers/(id)/export:导出容器内容,响应为Content-Type: application/octet-stream的 TAR 流。状态码200/404/500。POST /containers/(id)/copy:从容器拷贝文件/目录。请求体为{"Resource": "test.txt"},响应是 TAR 流(application/octet-stream)。状态码200/404/500。
3. 镜像端点(Images)
3.1 列出镜像 GET /images/(format)
format 可以是 json(默认)或 viz。
JSON 格式(GET /images/json?all=0)示例响应:
[
{
"Repository":"base",
"Tag":"ubuntu-12.10",
"Id":"b750fe79269d",
"Created":1364102658,
"Size":24653,
"VirtualSize":180116135
},
{
"Repository":"base",
"Tag":"ubuntu-quantal",
"Id":"b750fe79269d",
"Created":1364102658,
"Size":24653,
"VirtualSize":180116135
}
]
viz 格式(GET /images/viz)返回 Graphviz DOT 语言,用于可视化镜像层依赖图:
digraph docker {
"d82cbacda43a" -> "074be284591f"
"1496068ca813" -> "08306dc45919"
"08306dc45919" -> "0e7893146ac2"
"b750fe79269d" -> "1496068ca813"
base -> "27cf78414709" [style=invis]
"27cf78414709" -> "b750fe79269d"
"b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
"e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
base [style=invisible]
}
查询参数 all:1/True/true 或 0/False/false,显示全部镜像。状态码 200/400/500。
3.2 创建镜像(拉取或导入)POST /images/create
这是 pull 与 import 的统一入口。示例请求:
POST /images/create?fromImage=base HTTP/1.1
响应是一个逐条 JSON 行的进度流(这是后来 image_build、logs 端点流式响应的先驱形态):
{"status":"Pulling..."}
{"status":"Pulling", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...
从 registry 拉取私有镜像时,可用 X-Registry-Auth 头携带 base64 编码的 AuthConfig 对象。
查询参数:
fromImage– 要拉取的镜像名;fromSrc– 导入来源,-表示 stdin;repo– 仓库名;tag– 标签;registry– 指定从哪个 registry 拉取。
状态码:200/500。
3.3 其余镜像端点
POST /images/(name)/insert:把url处的文件插入到镜像name的path位置。示例:POST /images/test/insert?path=/usr&url=myurl;响应同样是{"status":"Inserting...","progress":"1/? (n/a)"}形式的进度流。GET /images/(name)/json:返回镜像低层信息,包括id、parent、created、container(构建该层的容器 Id)、container_config(Hostname、Cmd、Env、Volumes 等完整配置快照)和Size。状态码200/404/500。GET /images/(name)/history:返回镜像历史,形如{"Id":"b750fe79269d","Created":1364102658,"CreatedBy":"/bin/bash"}的数组。状态码200/404/500。POST /images/(name)/push:推送到 registry,响应为{"status":"Pushing...","progress":"1/? (n/a)"}进度流,同样支持X-Registry-Auth头。状态码200/404/500。POST /images/(name)/tag:打标签。示例:POST /images/test/tag?repo=myrepo&force=0&tag=v42。参数:repo(目标仓库)、force(默认 false)、tag(新标签名)。状态码201/400/404/409(冲突)/500。DELETE /images/(name):删除镜像。响应说明实际发生的动作——[{"Untagged": "3e2f21a89f"}, {"Deleted": "3e2f21a89f"}, {"Deleted": "53b4f83ac9"}],即先解除标签、再逐层删除。状态码200/404/409/500。GET /images/search:在 Docker Hub 搜索镜像。示例:GET /images/search?term=sshd,响应为{"Name":"cespare/sshd","Description":""}数组。状态码200/500。
4. 其他端点(Misc)
4.1 构建 POST /build
通过 stdin 传入构建上下文。请求体是一个 tar 流,支持 identity(不压缩)、gzip、bzip2、xz 压缩,归档根目录必须包含 Dockerfile,其余文件构成 build context(供 ADD 指令使用);Content-Type 应设为 application/tar。响应为构建输出的 {{ STREAM }}。
查询参数:
t– 构建成功后应用到结果镜像的仓库名(可含 tag);remote– 远端构建源 URI(git 或 HTTPS/HTTP);q– 静默模式,抑制冗长输出;nocache– 不使用缓存构建。
状态码:200/500。
4.2 认证校验 POST /auth
校验并获取默认用户名/邮箱。请求体:
{
"username": "hannibal",
"password": "xxxx",
"email": "hannibal@a-team.com",
"serveraddress": "https://index.docker.io/v1/"
}
状态码:200 或 204 均表示成功,500 服务端错误。
4.3 系统信息 GET /info 与版本 GET /version
GET /info 返回系统级统计(v1.6 示例响应):
{
"Containers":11,
"Images":16,
"Debug":false,
"NFd": 11,
"NGoroutines":21,
"MemoryLimit":true,
"SwapLimit":false,
"IPv4Forwarding":true
}
GET /version 返回 daemon 版本信息:
{
"Version":"0.2.2",
"GitCommit":"5a2a5cc+CHANGES",
"GoVersion":"go1.0.3"
}
两者状态码均为 200/500。
4.4 提交 POST /commit
从容器变更创建新镜像。示例请求:
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"ExposedPorts":{"22/tcp":{}}
}
响应 201,{"Id": "596069db4bf5"}。查询参数:container(源容器)、repo(仓库)、tag(标签)、m(commit message)、author(作者)。状态码 201/404/500。
4.5 事件流 GET /events
以流式(实时)或轮询(since 时间戳)方式获取事件。v1.6 中容器事件包括 create, destroy, die, export, kill, pause, restart, start, stop, unpause,镜像事件包括 untag, delete。
示例响应(逐条 JSON 流):
{"status": "create", "id": "dfdf82bd3881","from": "base:latest", "time":1374067924}
{"status": "start", "id": "dfdf82bd3881","from": "base:latest", "time":1374067924}
{"status": "stop", "id": "dfdf82bd3881","from": "base:latest", "time":1374067966}
{"status": "destroy", "id": "dfdf82bd3881","from": "base:latest", "time":1374067970}
状态码:200/500。
5. 核心技术:attach 流式多路复用协议
v1.6 文档最有长期价值的部分是 attach 的流帧协议——它规定了当容器未启用 TTY 时,如何在单一 HTTP 连接中同时传输 stdout 与 stderr:
- TTY 开启时:流就是进程 PTY 的原始数据加上客户端 stdin;
- TTY 关闭时:流被多路复用,帧格式为 HEADER + PAYLOAD。
HEADER 共 8 字节:第 1 字节标识流类型(写往 stdout 还是 stderr),最后 4 字节以 big-endian uint32 编码帧体大小:
header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
STREAM_TYPE 取值:0 = stdin(读取时输出到 stdout)、1 = stdout、2 = stderr。
文档给出的最简实现算法:
- 读 8 字节帧头;
- 按首字节选择 stdout 或 stderr;
- 从最后 4 字节取出帧大小;
- 读取该大小字节并输出到对应流;
- 回到第 1 步。
从源码结构看,这个协议在今天的 Moby 仓库中原封不动地活着。 api/pkg/stdcopy/stdcopy.go 定义了完全一致的常量:stdWriterPrefixLen = 8、stdWriterFdIndex = 0、stdWriterSizeIndex = 4(见 stdcopy.go),流类型常量 Stdin = 0、Stdout = 1、Stderr = 2 与文档逐字对应。StdCopy 解复用函数读取 8 字节头后,用 binary.BigEndian.Uint32 解析第 4~8 字节的帧大小(stdcopy.go),再按类型把帧体分发到 destOut 或 destErr——这正是文档第 5 节"IMPLEMENTATION"算法的工程化版本。现代 API 文档中所谓的 "Multiplexed stream format" 就是这份 v1.6 定义的直系后代;任何自行对接 Docker daemon 流式输出(logs、build、attach)的第三方工具,都必须实现这个解帧逻辑。
6. docker run 的内部实现
v1.6 文档第 3.1 节罕见地披露了 CLI 如何用 API 端点拼出 docker run 的完整流程:
- 创建容器:调用 create 端点;
- 处理镜像缺失:如果收到
404,说明镜像不存在——先尝试 pull,然后重试创建; - 启动容器:调用 start;
- 非分离模式:调用 attach,使用
logs=1(拿到容器启动以来的 stdout/stderr)加stream=1(持续跟踪); - 分离模式或仅附加 stdin:直接打印容器 Id。
这段流程解释了为什么 create 端点要返回 404(镜像不存在)而不仅是 400——CLI 依赖该状态码实现"缺镜像即拉取"的语义。它也是理解 docker run 并非单一系统调用,而是若干 REST 调用编排的结果的关键。
7. Hijacking 与 CORS
-
Hijacking:文档明确说明,在 v1.6 中
/attach使用 hijacking 在同一条 socket 上同时承载 stdin、stdout、stderr,且"This might change in the future"(未来可能改变)。从后续历史看,该机制一直保留至今——当前仓库客户端侧的 client/hijack.go 仍在处理连接劫持后的双向流。 -
CORS:如需允许跨源请求访问 Remote API,在 daemon 模式下加
--api-enable-cors启动参数,例如:$ docker -d -H="192.168.1.9:2375" --api-enable-cors注意这暗示 v1.6 时代可通过
-H将 API 绑定到 TCP 端口(默认 2375 的明文 TCP 在后续版本中因安全风险被反复强调需要 TLS)。
8. 小结:v1.6 对现代 Moby API 的意义
通读 api/docs/v1.6.md 可以确认:Docker Remote API 的基本盘——/containers/json、/containers/create + /containers/{id}/start 两段式生命周期、/images/create 进度流、/events 事件流、/attach 的 8 字节多路复用帧——全部在 v1.6 就定型了。研读这份历史文档的价值在于:它既是一份可逐端点复现的旧版接口契约(端点、参数、状态码齐全),又是理解当前 容器路由、版本协商中间件 与 stdcopy 流协议 实现意图的最佳入口。若你正在开发对接 Docker daemon 的 SDK 或运维工具,先读懂 v1.6 的协议骨架,再对照当前仓库 api/docs/ 下最新的 v1.5x.yaml 版本文件,就能同时掌握"为什么这样设计"与"现在应该怎么写"。
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 StartedRust0622
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