首页
/ 深入解析 Docker Remote API v1.6:从 Moby 早期 REST 接口到流式多路复用协议的设计源头

深入解析 Docker Remote API v1.6:从 Moby 早期 REST 接口到流式多路复用协议的设计源头

2026-09-04 22:29:50作者:廉彬冶Miranda

本文基于 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 有三个核心特征:

  1. Remote API 取代了 rcli(早期的本地客户端机制),所有交互都通过 HTTP/REST 进行;
  2. 默认监听在 unix:///var/run/docker.sock,但可以把 daemon 绑定到其他 host/port 或任意 Unix socket;
  3. 整体是 REST 风格,但对 attachpull 等复杂命令采用 HTTP hijacking——直接劫持 HTTP 连接来双向传输 stdinstdoutstderr

适用前提说明:当前 Moby 仓库中的 daemon 已不再支持 v1.6。从 daemon/config/config.go 可以看到,当前版本支持的最低 API 版本为 1.24MinAPIVersion),默认最低为 1.40,最高为 1.56MaxAPIVersion)。因此本文对 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 是容器的完整配置(HostnameUserMemoryMemorySwapTtyCmdImageVolumesWorkingDir 等);
  • 查询参数 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(可选):BindsLxcConfContainerIDFilePrivilegedPortBindingsLinksPublishAllPorts 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

这是 pullimport 的统一入口。示例请求:

POST /images/create?fromImage=base HTTP/1.1

响应是一个逐条 JSON 行的进度流(这是后来 image_buildlogs 端点流式响应的先驱形态):

{"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 处的文件插入到镜像 namepath 位置。示例:POST /images/test/insert?path=/usr&url=myurl;响应同样是 {"status":"Inserting...","progress":"1/? (n/a)"} 形式的进度流。
  • GET /images/(name)/json:返回镜像低层信息,包括 idparentcreatedcontainer(构建该层的容器 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/"
}

状态码:200204 均表示成功,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。

文档给出的最简实现算法

  1. 读 8 字节帧头;
  2. 按首字节选择 stdout 或 stderr;
  3. 从最后 4 字节取出帧大小;
  4. 读取该大小字节并输出到对应流;
  5. 回到第 1 步。

从源码结构看,这个协议在今天的 Moby 仓库中原封不动地活着。 api/pkg/stdcopy/stdcopy.go 定义了完全一致的常量:stdWriterPrefixLen = 8stdWriterFdIndex = 0stdWriterSizeIndex = 4(见 stdcopy.go),流类型常量 Stdin = 0Stdout = 1Stderr = 2 与文档逐字对应。StdCopy 解复用函数读取 8 字节头后,用 binary.BigEndian.Uint32 解析第 4~8 字节的帧大小(stdcopy.go),再按类型把帧体分发到 destOutdestErr——这正是文档第 5 节"IMPLEMENTATION"算法的工程化版本。现代 API 文档中所谓的 "Multiplexed stream format" 就是这份 v1.6 定义的直系后代;任何自行对接 Docker daemon 流式输出(logs、build、attach)的第三方工具,都必须实现这个解帧逻辑。

6. docker run 的内部实现

v1.6 文档第 3.1 节罕见地披露了 CLI 如何用 API 端点拼出 docker run 的完整流程:

  1. 创建容器:调用 create 端点;
  2. 处理镜像缺失:如果收到 404,说明镜像不存在——先尝试 pull,然后重试创建;
  3. 启动容器:调用 start;
  4. 非分离模式:调用 attach,使用 logs=1(拿到容器启动以来的 stdout/stderr)加 stream=1(持续跟踪);
  5. 分离模式或仅附加 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 版本文件,就能同时掌握"为什么这样设计"与"现在应该怎么写"。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384