首页
/ Moby Remote API v1.1 规范解析:取代 rcli 的第一代 RESTful 容器 API

Moby Remote API v1.1 规范解析:取代 rcli 的第一代 RESTful 容器 API

2026-09-06 16:22:50作者:邬祺芯Juliet

本文基于 Moby 仓库中的 api/docs/v1.1.md 展开,系统讲解 Remote API v1.1 的设计定位、全部端点(容器、镜像、杂项三大类共 22 个端点)的请求/响应格式与状态码约定,并结合当前仓库源码还原 attach 端点的 HTTP 连接劫持(hijacking)实现机制,帮助读者理解 Docker Engine REST API 的雏形以及 docker run 背后由多个端点拼成的调用链。

1. 历史定位:Remote API 如何取代 rcli

v1.1 是 Docker Remote API 最早期的版本规范之一,api/docs/v1.1.md 文档头部即标注 draft = true,属于 API 演进过程中保留下来的历史版本文档。原文档"简介"一节给出了三条核心定位:

  • Remote API 正在取代 rcli。rcli(remote CLI)是早期远程调用容器操作的 RPC 机制,Remote API 将其替换为标准的 HTTP + JSON 接口,让任何语言、任何平台都能通过 curl 之类的工具直接驱动 Docker 守护进程;
  • 守护进程默认监听端口为 2375,客户端通过该端口与 daemon 通信;
  • API 总体遵循 REST 风格,但对 attach、pull 等复杂命令会"劫持"(hijack)HTTP 连接,把 stdout/stdin/stderr 直接复用在同一条 TCP 连接上传输,而不是走 JSON 响应体。

这三条原则构成了此后所有 Engine API 版本的基础。需要特别指出的是版本兼容性现状:从当前仓库源码看,daemon/config/config.go 中定义的最小受支持 API 版本为 MinAPIVersion = "1.24"、最大为 MaxAPIVersion = "1.56"api/docs/README.md 也明确说明"对旧版本 API 的支持是 best-effort"。也就是说,v1.1 这份规范只应作为理解 API 演进起点的历史资料,实际对接当前 daemon 时应使用 v1.24 及以上的 API 版本;api/docs/CHANGELOG.md 则逐版本记录了从 v1.22 到 v1.56 的全部端点变更,可与本文对照阅读。

2. 容器端点(Containers)

2.1 列出容器:GET /containers/json

列出容器,支持四个查询参数:

参数 说明
all 1/True/true0/False/false,是否显示所有容器;默认只显示运行中的容器
limit 只显示最近创建的 limit 个容器,包含未运行的
since 只显示指定 Id 之后创建的容器,包含未运行的
before 只显示指定 Id 之前创建的容器,包含未运行的

示例请求

GET /containers/json?all=1&before=8dfafdbc3a40 HTTP/1.1

示例响应(返回 IdImageCommandCreatedStatus 字段的容器数组):

HTTP/1.1 200 OK
Content-Type: application/json

[
     {
             "Id": "8dfafdbc3a40",
             "Image": "ubuntu:latest",
             "Command": "echo 1",
             "Created": 1367854155,
             "Status": "Exit 0"
     },
     {
             "Id": "9cd87474be90",
             "Image": "ubuntu:latest",
             "Command": "echo 222222",
             "Created": 1367854155,
             "Status": "Exit 0"
     },
     {
             "Id": "3176a2479c92",
             "Image": "centos:latest",
             "Command": "echo 3333333333333333",
             "Created": 1367854154,
             "Status": "Exit 0"
     },
     {
             "Id": "4cb07b47f9fb",
             "Image": "fedora:latest",
             "Command": "echo 444444444444444444444444444444444",
             "Created": 1367854152,
             "Status": "Exit 0"
     }
]

状态码:200 无错误;400 参数错误;500 服务端错误。

2.2 创建容器:POST /containers/create

以 JSON 请求体携带容器配置,请求体即"config"(容器配置),包含主机名、用户、内存限制、标准流开关、命令、镜像、卷等字段。

示例请求

POST /containers/create HTTP/1.1
Content-Type: application/json

{
     "Hostname":"",
     "User":"",
     "Memory":0,
     "MemorySwap":0,
     "AttachStdin":false,
     "AttachStdout":true,
     "AttachStderr":true,
     "PortSpecs":null,
     "Tty":false,
     "OpenStdin":false,
     "StdinOnce":false,
     "Env":null,
     "Cmd":[
             "date"
     ],
     "Dns":null,
     "Image":"ubuntu",
     "Volumes":{},
     "VolumesFrom":""
}

示例响应

HTTP/1.1 201 Created
Content-Type: application/json

{
     "Id":"e90e34656806"
     "Warnings":[]
}

JSON 参数:config —— 容器的完整配置。

状态码:201 创建成功;404 无此容器(如镜像/依赖缺失场景);406 无法 attach(容器未运行);500 服务端错误。

2.3 检查容器:GET /containers/(id)/json

返回容器的低层详细信息。

示例请求

GET /containers/4fa6e0f0c678/json HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

{
             "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
             "Created": "2013-05-07T14:51:42.041847+02:00",
             "Path": "date",
             "Args": [],
             "Config": {
                     "Hostname": "4fa6e0f0c678",
                     "User": "",
                     "Memory": 0,
                     "MemorySwap": 0,
                     "AttachStdin": false,
                     "AttachStdout": true,
                     "AttachStderr": true,
                     "PortSpecs": null,
                     "Tty": false,
                     "OpenStdin": false,
                     "StdinOnce": false,
                     "Env": null,
                     "Cmd": [
                             "date"
                     ],
                     "Dns": null,
                     "Image": "ubuntu",
                     "Volumes": {},
                     "VolumesFrom": ""
             },
             "State": {
                     "Running": false,
                     "Pid": 0,
                     "ExitCode": 0,
                     "StartedAt": "2013-05-07T14:51:42.087658+02:01360",
                     "Ghost": false
             },
             "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
             "NetworkSettings": {
                     "IpAddress": "",
                     "IpPrefixLen": 0,
                     "Gateway": "",
                     "Bridge": "",
                     "PortMapping": null
             },
             "SysInitPath": "/home/kitty/go/src/github.com/docker/docker/bin/docker",
             "ResolvConfPath": "/etc/resolv.conf",
             "Volumes": {}
}

状态码:200 无错误;404 无此容器;500 服务端错误。可以看到 v1.1 的 inspect 返回结构已包含 StateNetworkSettingsVolumes 等分区,与今天 API 的 inspect 响应骨架一脉相承。

2.4 检查容器文件系统变更:GET /containers/(id)/changes

返回容器 id 文件系统中自镜像层以来发生变更的条目,每项含 PathKind(0=未变、1=已修改、2=已删除,见响应示例中的取值)。

示例请求

GET /containers/4fa6e0f0c678/changes HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

[
     {
             "Path": "/dev",
             "Kind": 0
     },
     {
             "Path": "/dev/kmsg",
             "Kind": 1
     },
     {
             "Path": "/test",
             "Kind": 1
     }
]

状态码:200 无错误;404 无此容器;500 服务端错误。

2.5 导出容器:GET /containers/(id)/export

导出容器 id 的内容,响应为 application/octet-stream 二进制流(tar 归档)。

示例请求

GET /containers/4fa6e0f0c678/export HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/octet-stream

{{ STREAM }}

状态码:200 无错误;404 无此容器;500 服务端错误。

2.6 启动容器:POST /containers/(id)/start

示例请求

POST /containers/e90e34656806/start HTTP/1.1

示例响应

HTTP/1.1 200 OK

状态码:200 无错误;404 无此容器;500 服务端错误。

2.7 停止容器:POST /containers/(id)/stop

示例请求

POST /containers/e90e34656806/stop?t=5 HTTP/1.1

示例响应

HTTP/1.1 204 OK

查询参数:t —— 在强制 kill 容器之前等待的秒数(宽限期)。

状态码:204 无错误;404 无此容器;500 服务端错误。

2.8 重启容器:POST /containers/(id)/restart

示例请求

POST /containers/e90e34656806/restart?t=5 HTTP/1.1

示例响应

HTTP/1.1 204 No Content

查询参数:t —— 在强制 kill 容器之前等待的秒数。

状态码:204 无错误;404 无此容器;500 服务端错误。

2.9 杀死容器:POST /containers/(id)/kill

示例请求

POST /containers/e90e34656806/kill HTTP/1.1

示例响应

HTTP/1.1 204 No Content

状态码:204 无错误;404 无此容器;500 服务端错误。

2.10 Attach 到容器:POST /containers/(id)/attach

attach 是 v1.1 中最具代表性的"非纯 REST"端点:响应是一个原始流(application/vnd.docker.raw-stream),连接被劫持后承载容器的标准流。

示例请求

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/true0/False/false,默认 false):

  • logs —— 是否返回日志;
  • stream —— 是否返回流;
  • stdin —— 若 stream=true,是否附加 stdin;
  • stdout —— 若 logs=true 则返回 stdout 日志,若 stream=true 则附加 stdout;
  • stderr —— 若 logs=true 则返回 stderr 日志,若 stream=true 则附加 stderr。

状态码:200 无错误;400 参数错误;404 无此容器;500 服务端错误。

2.11 通过 WebSocket Attach:GET /containers/(id)/attach/ws

通过 WebSocket 附加到容器,握手遵循 RFC 6455。

示例请求

GET /containers/e90e34656806/attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1

示例响应:握手成功后直接输出 {{ STREAM }} 数据流。

查询参数与 2.10 节的 logs/stream/stdin/stdout/stderr 完全一致。

状态码:200 无错误;400 参数错误;404 无此容器;500 服务端错误。

2.12 等待容器:POST /containers/(id)/wait

阻塞直到容器 id 停止,然后返回其退出码。

示例请求

POST /containers/16253994b7c4/wait HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

{"StatusCode": 0}

状态码:200 无错误;404 无此容器;500 服务端错误。

2.13 删除容器:DELETE /containers/(id)

从文件系统移除容器 id

示例请求

DELETE /containers/16253994b7c4?v=1 HTTP/1.1

示例响应

HTTP/1.1 204 OK

查询参数:v —— 为 1/True/true 时同时删除与该容器关联的卷,默认 false

状态码:204 无错误;400 参数错误;404 无此容器;500 服务端错误。

3. 镜像端点(Images)

3.1 列出镜像:GET /images/(format)

format 可为 json(默认)或 viz

json 格式示例请求

GET /images/json?all=0 HTTP/1.1

json 格式示例响应

HTTP/1.1 200 OK
Content-Type: application/json

[
     {
             "Repository":"ubuntu",
             "Tag":"precise",
             "Id":"b750fe79269d",
             "Created":1364102658
     },
     {
             "Repository":"ubuntu",
             "Tag":"12.04",
             "Id":"b750fe79269d",
             "Created":1364102658
     }
]

viz 格式示例请求

GET /images/viz HTTP/1.1

viz 格式示例响应(以 Graphviz digraph 文本返回镜像父子依赖关系图):

digraph docker {
"d82cbacda43a" -> "074be284591f"
"1496068ca813" -> "08306dc45919"
"08306dc45919" -> "0e7893146ac2"
"b750fe79269d" -> "1496068ca813"
base -> "27cf78414709" [style=invis]
"f71189fff3de" -> "9a33b36209ed"
"27cf78414709" -> "b750fe79269d"
"0e7893146ac2" -> "d6434d954665"
"d6434d954665" -> "d82cbacda43a"
base -> "e9aa60c60128" [style=invis]
"074be284591f" -> "f71189fff3de"
"b750fe79269d" [label="b750fe79269d\nubuntu",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
"e9aa60c60128" [label="e9aa60c60128\ncentos",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
"9a33b36209ed" [label="9a33b36209ed\nfedora",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
base [style=invisible]
}

查询参数:all —— 为 1/True/true 时显示所有镜像,默认只显示"中间"镜像。

状态码:200 无错误;400 参数错误;500 服务端错误。

3.2 创建镜像(拉取/导入):POST /images/create

通过从 registry 拉取或从源导入的方式创建镜像。

示例请求

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

示例响应(ndjson 进度流,逐行输出状态):

HTTP/1.1 200 OK
Content-Type: application/json

{"status":"Pulling..."}
{"status":"Pulling", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...

查询参数:

  • 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 HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

{"status":"Inserting..."}
{"status":"Inserting", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...

查询参数:

  • url —— 文件来源地址;
  • path —— 文件在镜像中的存储路径。

状态码:200 无错误;500 服务端错误。

3.4 检查镜像:GET /images/(name)/json

返回镜像 name 的低层信息。

示例请求

GET /images/centos/json HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

{
     "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
     "parent":"27cf784147099545",
     "created":"2013-03-23T22:24:18.818426-07:00",
     "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
     "container_config":
             {
                     "Hostname":"",
                     "User":"",
                     "Memory":0,
                     "MemorySwap":0,
                     "AttachStdin":false,
                     "AttachStdout":false,
                     "AttachStderr":false,
                     "PortSpecs":null,
                     "Tty":true,
                     "OpenStdin":true,
                     "StdinOnce":false,
                     "Env":null,
                     "Cmd": ["/bin/bash"],
                     "Dns":null,
                     "Image":"centos",
                     "Volumes":null,
                     "VolumesFrom":""
             }
}

状态码:200 无错误;404 无此镜像;500 服务端错误。

3.5 查看镜像历史:GET /images/(name)/history

返回镜像 name 的历史层记录。

示例请求

GET /images/fedora/history HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

[
     {
             "Id": "b750fe79269d",
             "Created": 1364102658,
             "CreatedBy": "/bin/bash"
     },
     {
             "Id": "27cf78414709",
             "Created": 1364068391,
             "CreatedBy": ""
     }
]

状态码:200 无错误;404 无此镜像;500 服务端错误。

3.6 推送镜像:POST /images/(name)/push

将镜像 name 推送到 registry。

示例请求

POST /images/test/push HTTP/1.1

示例响应(ndjson 进度流):

HTTP/1.1 200 OK
Content-Type: application/json

{"status":"Pushing..."}
{"status":"Pushing", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...

状态码:200 无错误;404 无此镜像;500 服务端错误。

3.7 打标签:POST /images/(name)/tag

为镜像 name 打上新的仓库标签。

示例请求

POST /images/test/tag?repo=myrepo&force=0&tag=v42 HTTP/1.1

示例响应

HTTP/1.1 201 OK

查询参数:

  • repo —— 要标记到的目标仓库;
  • force —— 1/True/true0/False/false,默认 false
  • tag —— 新标签名。

状态码:201 无错误;400 参数错误;404 无此镜像;409 冲突(标签已存在且未 force);500 服务端错误。

3.8 删除镜像:DELETE /images/(name)

从文件系统移除镜像 name

示例请求

DELETE /images/test HTTP/1.1

示例响应

HTTP/1.1 204 No Content

状态码:204 无错误;404 无此镜像;500 服务端错误。

3.9 搜索镜像:GET /images/search

在 Docker Hub 中搜索镜像。

示例请求

GET /images/search?term=sshd HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

[
     {
             "Name":"cespare/sshd",
             "Description":""
     },
     {
             "Name":"johnfuller/sshd",
             "Description":""
     },
     {
             "Name":"dhrp/mongodb-sshd",
             "Description":""
     }
]

查询参数:term —— 搜索词。

状态码:200 无错误;500 服务端错误。

4. 杂项端点(Misc)

4.1 从 stdin 构建镜像:POST /build

将 Dockerfile 内容通过 stdin 以流的形式发送,构建镜像。

示例请求

POST /build HTTP/1.1

{{ STREAM }}

示例响应

HTTP/1.1 200 OK

{{ STREAM }}

查询参数:t —— 构建成功后应用到结果镜像上的 tag。

状态码:200 无错误;500 服务端错误。

4.2 获取默认用户名和邮箱:GET /auth

示例请求

GET /auth HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

{
     "username":"hannibal",
     "email":"hannibal@a-team.com"
}

状态码:200 无错误;500 服务端错误。

4.3 校验并保存认证配置:POST /auth

提交用户名、密码、邮箱,校验认证配置并保存。

示例请求

POST /auth HTTP/1.1
Content-Type: application/json

{
     "username":"hannibal",
     "password:"xxxx",
     "email":"hannibal@a-team.com"
}

示例响应

HTTP/1.1 200 OK
Content-Type: text/plain

状态码:200 无错误;204 无错误;500 服务端错误。

4.4 系统信息:GET /info

示例请求

GET /info HTTP/1.1

示例响应

HTTP/1.1 200 OK
Content-Type: application/json

{
     "Containers":11,
     "Images":16,
     "Debug":false,
     "NFd": 11,
     "NGoroutines":21,
     "MemoryLimit":true,
     "SwapLimit":false
}

状态码:200 无错误;500 服务端错误。

4.5 版本信息:GET /version

示例请求

GET /version HTTP/1.1

示例响应(v1.1 时代守护进程版本还是 0.x):

HTTP/1.1 200 OK
Content-Type: application/json

{
     "Version":"0.2.2",
     "GitCommit":"5a2a5cc+CHANGES",
     "GoVersion":"go1.0.3"
}

状态码:200 无错误;500 服务端错误。

4.6 提交容器为新镜像:POST /commit

基于容器的变更创建新镜像。

示例请求

POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json

{
    "Cmd": ["cat", "/world"],
    "PortSpecs":["22"]
}

示例响应

HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream

{"Id": "596069db4bf5"}

查询参数:

  • container —— 源容器;
  • repo —— 仓库;
  • tag —— 标签;
  • m —— commit 说明信息;
  • author —— 作者(例如 "John Hannibal Smith hannibal@a-team.com")。

状态码:201 无错误;404 无此容器;500 服务端错误。

5. 深入理解:docker run 背后的端点组合

原文档"Going further"一节给出了 docker run 的端点调用步骤,这也是理解"一条命令 = 多个 API 调用"这一 Engine API 设计哲学的关键:

  1. 创建容器:调用 POST /containers/create
  2. 处理镜像缺失:如果返回状态码 404,说明镜像不存在——先调用 POST /images/create 拉取,然后重试创建容器;
  3. 启动容器:调用 POST /containers/(id)/start
  4. 前台模式:如果未处于 -d 分离模式,则调用 POST /containers/(id)/attach,使用 logs=1(以便获得容器启动以来的 stdout 与 stderr)与 stream=1
  5. 分离模式(或仅附加 stdin 时):直接显示容器 Id。

以 v1.1 语义为例,docker run -it ubuntu bash 前台运行即对应"create →(必要时 images/create 拉取)→ start → attach(logs=1, stream=1, stdin=1, stdout=1, stderr=1)"这条链路;docker run -d 则止于 start 并输出容器 Id。客户端库中今天仍能看到这条链路对应的独立方法,例如 client/container_create.goclient/container_start.goclient/image_pull.go

6. Hijacking 机制:从 v1.1 设计到当前源码实现

v1.1 文档在结尾明确指出:"在 API 的这个版本中,/attach 使用 hijacking 在同一条 socket 上同时传输 stdin、stdout 和 stderr,这一行为未来可能会改变。"这句话在后续版本中兑现的方式是:hijacking 不仅没有消失,反而成为 attach/exec 的固定机制,并在 client/hijack.go 中沉淀为客户端标准实现(DialHijackpostHijacked 等函数),其核心思路是——

  • 先发送普通 HTTP 请求,服务端在返回 101 UPGRADED200 OK 后"劫持"底层 TCP 连接;
  • 客户端把该连接当作裸 net.Conn 使用,stdin/stdout/stderr 直接以原始字节流(或 stdcopy 多路复用格式)在这条连接上双向流动,绕开 HTTP 响应体语义。

从当前仓库源码结构看,这一机制在守护进程侧的对应实现位于 daemon/server/router/container/container_routes.gopostContainersAttach 中:路由在 daemon/server/router/container/container.go 注册为 POST /containers/{name:.*}/attach。处理函数先断言 http.ResponseWriter 实现了 http.Hijacker 接口,随后调用 hijacker.Hijack() 取得裸连接,向其中写入手写的 HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream 响应头(若客户端发起 WebSocket 升级则改写为 101 UPGRADED),最后把 stdin/stdout/stderr 三个句柄全部指向同一条连接交给 ContainerAttach 后端逻辑。这正是 v1.1 文档中"{{ STREAM }}"占位符背后的真实行为,也解释了为什么 attach 的响应无法按普通 JSON REST 响应处理。

同时,v1.1 文档中出现的 Content-Type: application/vnd.docker.raw-stream、ndjson 进度流({"status":"Pulling..."}...)、404/400/406/409/500 状态码约定等,均被后续版本完整继承,可在 api/swagger.yaml 的最新规范中逐一对应。

7. 延伸阅读与版本对照

  • 版本演进:api/docs/CHANGELOG.md 按 API 版本逐条记录端点变更(例如 v1.52 起 GET /containers/{id}/json 移除了大量已废弃的 NetworkSettings 字段),是理解 v1.1 各端点如何演化到 v1.56 的权威索引;
  • 版本文档组织约定:api/docs/README.md 说明本目录按版本存放规范,v1.24 及以后转为 Swagger/OpenAPI 格式;
  • 当前可协商的版本区间:daemon/config/config.go 定义 MaxAPIVersion = "1.56"MinAPIVersion = "1.24"(默认最低版本 defaultMinAPIVersion = "1.40",可通过 daemon 配置 min-api-version 调整),daemon/server/middleware/version.go 中的 VersionMiddleware 则负责在每次请求时校验客户端声明的 API 版本并注入上下文——低于最小版本会被拒绝("client version ... is too old"),高于默认版本同样会被拒绝。

综上,api/docs/v1.1.md 作为 Remote API 的第一代规范,确立了"HTTP + JSON 为主、连接劫持为辅、2375 端口、逐端点 REST 化"的整体架构。它的每个端点——从 containers/json 的过滤参数到 commit 的查询参数——都在后续版本中被保留或平滑演进,是读懂 Moby Engine API 全貌的起点文档。

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