首页
/ Moby Remote API v1.7 规格详解:容器/镜像端点全解与 Attach 多路复用流协议

Moby Remote API v1.7 规格详解:容器/镜像端点全解与 Attach 多路复用流协议

2026-09-04 21:40:48作者:乔或婵

本文以 Moby 仓库中恢复归档的历史 API 规范 api/docs/v1.7.md 为主体,完整梳理 Docker Remote API v1.7 的全部端点(容器、镜像、杂项三大类共 24+ 个接口)及其请求/响应示例、查询参数与状态码,并结合当前仓库源码佐证其中仍存活的底层机制:/var/run/docker.sock 默认监听、attach 流的 8 字节帧头多路复用协议(对应 api/pkg/stdcopy)、HTTP 连接劫持(对应 client/hijack.go)与 X-Registry-Auth 头部编解码(对应 api/pkg/authconfig),帮助读者既读懂这份早期 REST API 规格,又看清哪些设计延续到了今天的 Moby。

需要说明的历史定位:该文档是 API v1.7(Docker 0.x 时代)的规范快照,通过提交 "api/docs: restore API versions v1.0 - v1.13" 恢复进仓库,作为历史版本存档。当前仓库的 Go 客户端最低只支持 API 1.40、最高 1.56(见 client/client.go 中的 MinAPIVersion/MaxAPIVersion 常量),因此 v1.7 接口不可被现代客户端直接调用,但其端点结构、状态码语义和流协议是理解整个 Docker API 演进的起点。

1. 总体设计:REST + 连接劫持

v1.7 文档开篇给出三条总体设计原则:

  • Remote API 取代 rcli。rcli 是早期基于 RPC 的远程调用方案,Remote API 改用 HTTP 语义更通用的 REST 风格接口;
  • 默认监听 Unix socket。守护进程默认监听 unix:///var/run/docker.sock,也可以绑定到其他 host/port 或另一个 Unix socket;
  • 倾向 REST,但允许劫持。对于 attachpull 这类需要双向传输 stdin/stdout/stderr 的复杂命令,HTTP 连接会被 hijack(劫持),后续流量不再受 HTTP 报文边界约束。

第一条原则在今天的仓库中依然成立:守护进程的默认 socket 路径定义于 daemon/pkg/opts/hosts.goDefaultUnixSocket = "/var/run/docker.sock"daemon/command/daemon.go 中的 defaultAPISocketPath 函数在 rootless(rootlessKit)场景下则改为监听 $XDG_RUNTIME_DIR/docker.sock——这正是对文档"可以绑定到另一个 Unix socket"的落地。

第三条原则在 client/hijack.go 中能看到完整继承:postHijacked 发起 POST 后调用 setupHijackConn,后者设置 Connection: UpgradeUpgrade: <proto> 请求头完成协议升级,等待服务端返回 101 Switching Protocols,随后把裸连接连同响应头里的 Content-Type 一并交给上层——这个 Content-Type 决定流是原始流还是多路复用流,与 v1.7 attach 端点返回的 application/vnd.docker.raw-stream 语义一脉相承。

2. 容器端点(v1.7 共 15 个)

2.1 列出容器:GET /containers/json

示例请求

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

示例响应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
    },
    {
        "Id": "3176a2479c92",
        "Image": "base:latest",
        "Command": "echo 3333333333333333",
        "Created": 1367854154,
        "Status": "Exit 0",
        "Ports": [],
        "SizeRw": 12288,
        "SizeRootFs": 0
    },
    {
        "Id": "4cb07b47f9fb",
        "Image": "base:latest",
        "Command": "echo 444444444444444444444444444444444",
        "Created": 1367854152,
        "Status": "Exit 0",
        "Ports": [],
        "SizeRw": 12288,
        "SizeRootFs": 0
    }
]

查询参数:

参数 说明
all 1/True/true0/False/false,显示全部容器;默认只显示运行中的(默认 false)
limit 只显示最近创建的 limit 个容器,包括非运行中的
since 只显示 Id 之后创建的容器,包括非运行中的
before 只显示 Id 之前创建的容器,包括非运行中的
size 1/True/true0/False/false,显示容器大小(对应响应中的 SizeRw/SizeRootFs 字段)

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

2.2 创建容器:POST /containers/create

示例请求

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":"base",
    "Volumes":{"/tmp": {}},
    "VolumesFrom":"",
    "WorkingDir":"",
    "ExposedPorts":{"22/tcp": {}}
}

示例响应

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

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

请求体是"容器配置(config)"对象:Memory/MemorySwap 是内存与内存+交换上限(0 表示不限制),AttachStdin/Stdout/Stderr 决定 attach 时可用的流,Tty 决定是否分配伪终端(该字段直接影响后文 attach 流是原始流还是多路复用流),ExposedPorts 声明镜像级端口暴露。

状态码:201 成功;404 容器不存在;406 无法 attach(容器未运行);500 服务端错误。

对照现代 API:v1.7 把"创建配置"与"主机配置"拆在 create/start 两个端点里,这正是后来 ContainerCreateConfig + HostConfig 分离结构的雏形。

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

返回容器的低层信息。

示例请求

GET /containers/4fa6e0f0c678/json HTTP/1.1

示例响应(节选关键字段):

{
    "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": "base",
        "Volumes": {},
        "VolumesFrom": "",
        "WorkingDir": ""
    },
    "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": {}
}

响应结构是三层:Config(创建时的静态配置)、State(运行态:RunningPidExitCodeStartedAtGhost)、NetworkSettings(早期单体网络模型下的 IP 前缀、网关、桥接与端口映射)。状态码:200 成功;404 容器不存在;500 服务端错误。

2.4 列出容器内进程:GET /containers/(id)/top

示例请求

GET /containers/4fa6e0f0c678/top HTTP/1.1

示例响应

{
    "Titles": ["USER","PID","%CPU","%MEM","VSZ","RSS","TTY","STAT","START","TIME","COMMAND"],
    "Processes": [
        ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"],
        ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"]
    ]
}

查询参数:ps_args —— 传给 ps 的参数(例如 aux),TitlesProcesses 的每一行即按该格式对齐。状态码:200/404/500

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

示例请求

GET /containers/4fa6e0f0c678/changes HTTP/1.1

示例响应

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

Kind 是变更类型编号(0/1/2 分别对应未变更/修改/删除的编码)。状态码:200/404/500

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

示例请求

GET /containers/4fa6e0f0c678/export HTTP/1.1

示例响应HTTP/1.1 200 OKContent-Type: application/octet-stream,响应体为 {{ TAR STREAM }}(tar 数据流)。状态码:200/404/500

2.7 生命周期控制:start / stop / restart / kill

启动 POST /containers/(id)/start。与创建不同,start 可附带"主机配置(hostConfig)":

POST /containers/(id)/start HTTP/1.1
Content-Type: application/json

{
    "Binds":["/tmp:/tmp"],
    "LxcConf":[{"Key":"lxc.utsname","Value":"docker"}],
    "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] },
    "Privileged":false,
    "PublishAllPorts":false
}

注意文档特别强调:Binds 必须引用容器创建时已定义的 Volumes。响应为 HTTP/1.1 204 No Content

停止 POST /containers/(id)/stop

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

重启 POST /containers/(id)/restart

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

stop 与 restart 共享查询参数 t —— 超时秒数,到时未退出则 kill。响应均为 HTTP/1.1 204 No Content

强制终止 POST /containers/(id)/kill

POST /containers/e90e34656806/kill HTTP/1.1

响应 HTTP/1.1 204 No Content。四个端点状态码一致:204 成功;404 容器不存在;500 服务端错误。

2.8 附加到容器: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/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 服务端错误。

流细节(本规范的核心算法,必须完整掌握)

  • 创建容器时若启用了 TTY,流就是进程 PTY 与客户端 stdin 的原始数据(raw stream);
  • 若未启用 TTY,流会做多路复用(multiplexed),把 stdout 与 stderr 交织在同一条字节流里,格式为帧头(Header)+ 载荷(Payload)
header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}

其中 STREAM_TYPE0 = stdin(读取时写入 stdout)、1 = stdout、2 = stderr;SIZE1..SIZE4 为 big-endian 编码的 uint32 帧长。文档给出的最简实现循环:

  1. 读 8 字节帧头;
  2. 根据第 1 个字节选择 stdout 或 stderr;
  3. 从后 4 字节解析帧大小;
  4. 读取该大小的载荷并输出到对应流;
  5. 回到第 1 步。

源码印证:这段 2013 年的协议描述与今天仓库中的 api/pkg/stdcopy/stdcopy.go 完全同构。该包定义 Stdin=0Stdout=1Stderr=2 三个流类型常量(并新增 Systemerr=3 用于守护进程侧错误),帧头长度 stdWriterPrefixLen = 8、流类型位于偏移 0、帧长位于偏移 4 且按 binary.BigEndian.Uint32 解码;StdCopy 函数实现的就是"读帧头 → 按第 1 字节分流 → 按后 4 字节读帧长 → 输出到 destOut/destErr"的循环。可以说 v1.7 文档里的手工解析步骤,就是现代 Go SDK 中 stdcopy.StdCopy 的算法原型,这套帧格式沿用至今未变。

2.9 通过 WebSocket 附加:GET /containers/(id)/attach/ws

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

响应体直接是 {{ STREAM }}。该端点按 RFC 6455 完成 WebSocket 握手,查询参数与状态码同上一节 attach。它是早期浏览器端交互(如当年的 Web UI)的通道,后续版本被 POST .../attach + 原生 WebSocket 升级取代。

2.10 等待容器退出: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.11 删除容器:DELETE /containers/(id)

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

查询参数:v —— 1/True/true0/False/false,是否同时移除容器关联的卷,默认 false。响应 HTTP/1.1 204 No Content。状态码:204 成功;400 参数错误;404 容器不存在;500 服务端错误。

2.12 从容器复制文件:POST /containers/(id)/copy

POST /containers/4fa6e0f0c678/copy HTTP/1.1
Content-Type: application/json

{
    "Resource": "test.txt"
}

响应:HTTP/1.1 200 OKContent-Type: application/octet-stream,响应体为 {{ TAR STREAM }}(即后来 docker cpGET /containers/(id)/archive 的前身)。状态码:200/404/500

3. 镜像端点

3.1 列出镜像:GET /images/json

GET /images/json?all=0 HTTP/1.1
[
  {
     "RepoTags": ["ubuntu:12.04", "ubuntu:precise", "ubuntu:latest"],
     "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
     "Created": 1365714795,
     "Size": 131506275,
     "VirtualSize": 131506275
  },
  {
     "RepoTags": ["ubuntu:12.10", "ubuntu:quantal"],
     "ParentId": "27cf784147099545",
     "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
     "Created": 1364102658,
     "Size": 24653,
     "VirtualSize": 180116135
  }
]

Size 是镜像自身层大小,VirtualSize 是含父层在内的总大小;RepoTags 数组体现一个镜像 ID 可挂多个标签、无标签时 RepoTags 为空(即 dangling image)。

3.2 创建镜像(pull 或 import):POST /images/create

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

响应是逐行的 JSON 进度流:

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

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

从 registry 拉取时,可用 X-Registry-Auth 请求头携带 base64 编码的 AuthConfig 对象。

查询参数:

参数 说明
fromImage 要拉取的镜像名
fromSrc 要导入的来源,- 表示 stdin
repo 目标仓库
tag 目标标签
registry 要拉取的 registry

请求头:X-Registry-Auth —— base64 编码的 AuthConfig 对象。状态码:200 成功;500 服务端错误。

源码印证:这个请求头的编解码规则在今天的仓库中由 api/pkg/authconfig/authconfig.go 承担——Encoderegistry.AuthConfig 序列化为 base64url(RFC 4648 第 5 节)编码的 JSON 字符串用于 X-Registry-Auth 头;Decode 反向解码且"即使出错也返回空 AuthConfig"的兼容性策略,注释明确写着是为了兼容旧客户端与旧 API 版本。v1.7 文档确立的"认证走请求头、正文走进度流"模式即为今日规范。

3.3 向镜像插入文件:POST /images/(name)/insert

url 取文件插入到镜像 namepath 路径:

POST /images/test/insert?path=/usr&url=myurl HTTP/1.1

响应同样是逐行 JSON 进度流({"status":"Inserting..."} 等)。查询参数:url(文件来源)、path(存储路径)。状态码:200/500。该端点是早期"不构建、直接改镜像"的能力,后由 Dockerfile + build 流程取代并从 API 中移除。

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

GET /images/base/json HTTP/1.1
{
    "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":"base",
            "Volumes":null,
            "VolumesFrom":"",
            "WorkingDir":""
        },
    "Size": 6824592
}

字段要点:parent 构成镜像层链;container 记录该层由哪个容器 commit 而来;container_config 快照了构建该层时容器的运行配置(注意此层 Tty:trueOpenStdin:true,与 2.2 节 create 示例的差异)。状态码:200/404/500

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

GET /images/base/history HTTP/1.1
[
    {"Id": "b750fe79269d", "Created": 1364102658, "CreatedBy": "/bin/bash"},
    {"Id": "27cf78414709", "Created": 1364068391, "CreatedBy": ""}
]

状态码:200/404/500

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

POST /images/test/push HTTP/1.1

响应为逐行 JSON 进度流({"status":"Pushing..."} 等),请求头同样支持 X-Registry-Auth 携带 base64 编码 AuthConfig。状态码:200 成功;404 镜像不存在;500 服务端错误。

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

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

响应 HTTP/1.1 201 OK。查询参数:repo(目标仓库)、force(默认 false)、tag(新标签名)。状态码:201 成功;400 参数错误;404 镜像不存在;409 冲突;500 服务端错误。

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

DELETE /images/test HTTP/1.1
HTTP/1.1 200 OK
Content-type: application/json

[
 {"Untagged": "3e2f21a89f"},
 {"Deleted": "3e2f21a89f"},
 {"Deleted": "53b4f83ac9"}
]

响应逐条报告操作结果:先摘除标签(Untagged),再逐层删除(Deleted)——这里能看到"删除一个带标签镜像实际会连带删掉其独有层"的语义。状态码:200 成功;404 镜像不存在;409 冲突;500 服务端错误。

3.9 搜索镜像:GET /images/search

在 Docker Hub 中搜索镜像。文档特别注明:从 API v1.6 起响应键名已变更,以对齐 registry 服务端返回给守护进程的 JSON。

GET /images/search?term=sshd HTTP/1.1
[
    {
        "description": "",
        "is_official": false,
        "is_trusted": false,
        "name": "wma55/u1210sshd",
        "star_count": 0
    },
    {
        "description": "",
        "is_official": false,
        "is_trusted": false,
        "name": "jdswinbank/sshd",
        "star_count": 0
    },
    {
        "description": "",
        "is_official": false,
        "is_trusted": false,
        "name": "vgauthier/sshd",
        "star_count": 0
    }
]

查询参数:term(搜索词)。状态码:200/500

3.10 导出/加载镜像 tar 包:GET /images/(name)/getPOST /images/load

导出GET /images/ubuntu/get 返回包含该仓库全部镜像与元数据的 tar 包,Content-Type: application/x-tar,状态码 200/500

加载POST /images/load,请求体即 tar 包,把一组镜像与标签载入本地仓库,响应 HTTP/1.1 200 OK,状态码 200/500。二者即今天 docker save / docker load 的 API 原型。

4. 杂项端点

4.1 通过 stdin 构建镜像:POST /build

POST /build HTTP/1.1

{{ TAR STREAM }}

响应:HTTP/1.1 200 OKContent-Type: application/json,响应体为 {{ STREAM }}(构建进度流)。

约束:tar 流必须使用 identity(不压缩)、gzip、bzip2、xz 之一压缩(原文如此表述,实际指这几种编码之一),且归档根部必须包含名为 Dockerfile 的文件;归档中的其他文件都可作为构建上下文在 ADD 指令中使用。

查询参数:

参数 说明
t 构建成功后应用到镜像的仓库名(可含标签)
remote 构建来源 URI(git 或 HTTPS/HTTP)
q 抑制详细构建输出
nocache 构建时不使用缓存

请求头:Content-type 应设为 application/tar。状态码:200/500

4.2 校验认证配置:POST /auth

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

{
    "username":" hannibal",
    "password: "xxxx",
    "email": "hannibal@a-team.com",
    "serveraddress": "https://index.docker.io/v1/"
}

响应为 HTTP/1.1 200 OK204(均为成功,200 时返回校验后的 username/email 信息)。状态码:200/204 成功;500 服务端错误。该端点是 docker login 校验凭据的通道,请求体即后来 registry.AuthConfig 的前身(对照 api/types/registry 下今天的 AuthConfig 类型可看到字段演化)。

4.3 系统信息:GET /info

GET /info HTTP/1.1
{
    "Containers":11,
    "Images":16,
    "Debug":false,
    "NFd": 11,
    "NGoroutines":21,
    "MemoryLimit":true,
    "SwapLimit":false,
    "IPv4Forwarding":true
}

字段含容器/镜像计数、守护进程调试开关、打开的文件描述符数与 goroutine 数(典型的 Go 运行时自省指标)、以及内存/交换限制与 IPv4 转发能力。状态码:200/500

4.4 版本信息:GET /version

GET /version HTTP/1.1
{
    "Version":"0.2.2",
    "GitCommit":"5a2a5cc+CHANGES",
    "GoVersion":"go1.0.3"
}

状态码:200/500。这个端点同时是 API 版本协商的载体:现代客户端正是通过 /version(Ping)拿到守护进程的 ApiVersion 后再进行协商,见 client/client.gonegotiateAPIVersion 逻辑。

4.5 从容器变更创建镜像:POST /commit

POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream

{"Id": "596069db4bf5"}

查询参数:

参数 说明
container 源容器
repo 仓库
tag 标签
m 提交说明
author 作者(例如 "John Hannibal Smith hannibal@a-team.com")
run 镜像运行时自动应用的配置,如 {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}

状态码:201 成功;404 容器不存在;500 服务端错误。响应中的新 Id 即 3.4 节 inspect 中 container_config 快照的来源机制(commit 把容器当前状态固化为新镜像层)。

4.6 监控事件:GET /events

以流式(实时)或轮询(带 since)方式获取守护进程事件。容器报告的事件:create, destroy, die, export, kill, pause, restart, start, stop, unpause;镜像报告:untag, delete

GET /events?since=1374067924
HTTP/1.1 200 OK
Content-Type: application/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}

查询参数:since —— 轮询用的时间戳。状态码:200/500

5. 深入理解:docker run 背后的端点编排

v1.7 文档第 3.1 节给出了 docker run 客户端的完整编排步骤,这是理解"一条命令 = 一组 API 调用"的关键:

  1. 创建容器POST /containers/create);
  2. 若返回 404,说明镜像不存在:
    • 尝试拉取(POST /images/create);
    • 然后重试创建容器;
  3. 启动容器POST /containers/(id)/start);
  4. 非分离(detached)模式下:
    • 附加到容器,使用 logs=1(以获得容器启动以来的 stdout 与 stderr)和 stream=1
  5. 分离模式或仅附加 stdin 时:
    • 打印容器 ID。

这套"create → 404 则 pull → 重试 → start → attach"的幂等重试模式,至今仍是各类 Docker 客户端 SDK 实现 run 语义的标准流程。

5.1 Hijacking 机制

v1.7 文档第 3.2 节明确指出:本版本 API 中 /attach 使用 hijacking 在同一 socket 上同时传输 stdin、stdout 与 stderr,"未来可能改变"。从当前仓库源码看,这个机制不但没有消失,反而被标准化了:

  • client/hijack.gosetupHijackConn 通过 Connection: Upgrade / Upgrade 头完成协议切换,并针对长空闲连接(长时间无输出的命令)设置 TCP KeepAlive(30 秒周期),注释里说明这是为了规避某些网络环境下 ECONNTIMEOUT 导致的客户端状态不确定——这正是 v1.7 时代 hijack 流在弱网络上踩过的坑的工程化修复;
  • 响应头 Content-TypeHijackedResponse.MediaType() 暴露给上层,用于判定拿到的是原始流还是多路复用流,进而决定是否走 api/pkg/stdcopy 拆帧。

5.2 跨域请求(CORS)

文档第 3.3 节:允许对远程 API 的跨域请求,需要在守护进程模式启动时加 --api-enable-cors 标志:

$ docker -d -H="192.168.1.9:2375" --api-enable-cors

需要说明:--api-enable-cors 是 v1.7 时代的守护进程标志,在当前仓库的 Go 源码中已检索不到该标志的实现(grep api-enable-cors/APIEnableCORS 无结果),它属于历史演进中被移除的选项;今天跨域支持由守护进程配置文件/--cors-header 等形式承载。引用该段落时应以其历史语境为准。

6. 结语:从 v1.7 到现代 Moby API 的传承

通读 api/docs/v1.7.md 并结合仓库源码,可以清晰地看到三条延续至今的主线:

  1. 端点资源模型/containers/*/images/*/build/events 的资源划分与"逐行 JSON 进度流"响应风格,直接演化为今天 api/swagger.yamlapi/docs/v1.25.yaml 等现代版本的 OpenAPI 规范;
  2. 流协议:attach 的 8 字节帧头多路复用格式([STREAM_TYPE,0,0,0,SIZE1..SIZE4],big-endian uint32)被 api/pkg/stdcopy 逐字节实现并沿用至今,是该文档最有长期价值的算法资产;
  3. 认证与版本协商X-Registry-Auth base64 头由 api/pkg/authconfig 承接,/version 端点则是客户端协商 API 版本(当前客户端支持 1.40~1.56)的入口,见 client/client.go

同时应注意适用边界:v1.7 中的 insert、WebSocket attach(attach/ws)、--api-enable-cors 等均为已被取代的历史特性;若要在当前 Moby 上编写客户端,应以 api/docs 下最高版本规范(如 api/docs/v1.55.yaml)为准,本文档的价值在于理解 API 演进的源头。

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