Moby Remote API v1.7 规格详解:容器/镜像端点全解与 Attach 多路复用流协议
本文以 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,但允许劫持。对于
attach、pull这类需要双向传输stdin/stdout/stderr的复杂命令,HTTP 连接会被 hijack(劫持),后续流量不再受 HTTP 报文边界约束。
第一条原则在今天的仓库中依然成立:守护进程的默认 socket 路径定义于 daemon/pkg/opts/hosts.go 的 DefaultUnixSocket = "/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: Upgrade 与 Upgrade: <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/true 或 0/False/false,显示全部容器;默认只显示运行中的(默认 false) |
limit |
只显示最近创建的 limit 个容器,包括非运行中的 |
since |
只显示 Id 之后创建的容器,包括非运行中的 |
before |
只显示 Id 之前创建的容器,包括非运行中的 |
size |
1/True/true 或 0/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(运行态:Running、Pid、ExitCode、StartedAt、Ghost)、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),Titles 与 Processes 的每一行即按该格式对齐。状态码: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 OK,Content-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
查询参数(均为 1/True/true 或 0/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_TYPE:0 = stdin(读取时写入 stdout)、1 = stdout、2 = stderr;SIZE1..SIZE4 为 big-endian 编码的 uint32 帧长。文档给出的最简实现循环:
- 读 8 字节帧头;
- 根据第 1 个字节选择 stdout 或 stderr;
- 从后 4 字节解析帧大小;
- 读取该大小的载荷并输出到对应流;
- 回到第 1 步。
源码印证:这段 2013 年的协议描述与今天仓库中的 api/pkg/stdcopy/stdcopy.go 完全同构。该包定义 Stdin=0、Stdout=1、Stderr=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/true 或 0/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 OK,Content-Type: application/octet-stream,响应体为 {{ TAR STREAM }}(即后来 docker cp 与 GET /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 承担——Encode 将 registry.AuthConfig 序列化为 base64url(RFC 4648 第 5 节)编码的 JSON 字符串用于 X-Registry-Auth 头;Decode 反向解码且"即使出错也返回空 AuthConfig"的兼容性策略,注释明确写着是为了兼容旧客户端与旧 API 版本。v1.7 文档确立的"认证走请求头、正文走进度流"模式即为今日规范。
3.3 向镜像插入文件:POST /images/(name)/insert
从 url 取文件插入到镜像 name 的 path 路径:
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:true、OpenStdin: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)/get 与 POST /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
响应:HTTP/1.1 200 OK,Content-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 OK 或 204(均为成功,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.go 的 negotiateAPIVersion 逻辑。
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 调用"的关键:
- 创建容器(
POST /containers/create); - 若返回 404,说明镜像不存在:
- 尝试拉取(
POST /images/create); - 然后重试创建容器;
- 尝试拉取(
- 启动容器(
POST /containers/(id)/start); - 非分离(detached)模式下:
- 附加到容器,使用
logs=1(以获得容器启动以来的 stdout 与 stderr)和stream=1;
- 附加到容器,使用
- 分离模式或仅附加 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.go 的
setupHijackConn通过Connection: Upgrade/Upgrade头完成协议切换,并针对长空闲连接(长时间无输出的命令)设置 TCP KeepAlive(30 秒周期),注释里说明这是为了规避某些网络环境下 ECONNTIMEOUT 导致的客户端状态不确定——这正是 v1.7 时代 hijack 流在弱网络上踩过的坑的工程化修复; - 响应头
Content-Type被HijackedResponse.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 并结合仓库源码,可以清晰地看到三条延续至今的主线:
- 端点资源模型:
/containers/*、/images/*、/build、/events的资源划分与"逐行 JSON 进度流"响应风格,直接演化为今天 api/swagger.yaml 与 api/docs/v1.25.yaml 等现代版本的 OpenAPI 规范; - 流协议:attach 的 8 字节帧头多路复用格式(
[STREAM_TYPE,0,0,0,SIZE1..SIZE4],big-endian uint32)被 api/pkg/stdcopy 逐字节实现并沿用至今,是该文档最有长期价值的算法资产; - 认证与版本协商:
X-Registry-Authbase64 头由 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 演进的源头。
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