首页
/ Docker Compose alpha publish 深度解析:把 Compose 项目打包为 OCI 制品并发布到镜像仓库

Docker Compose alpha publish 深度解析:把 Compose 项目打包为 OCI 制品并发布到镜像仓库

2026-09-05 23:40:01作者:袁立春Spencer

本篇指南围绕 Docker Compose CLI 的实验性命令 docker compose alpha publish 展开,完整覆盖其命令用法、全部参数、发布前的安全预检、OCI 图层(layer)的组织方式,以及 OCI 1.0/1.1 清单兼容机制。读完后,你将掌握如何把一个 Compose 项目连同其环境文件、digest 锁定信息一起发布为标准 OCI 制品,并理解其在源码层面如何通过 pkg/compose/publish.gointernal/oci/push.go 实现这一流程。

一、命令定位:一条实验性命令

publish 命令挂载在 alpha 实验性命令组下,该组在源码中被显式标记为隐藏(Hidden: true)并带有 experimentalCLI: "true" 注解(见 cmd/compose/alpha.go),与 vizgenerate 并列。其命令签名为:

docker compose alpha publish [OPTIONS] REPOSITORY[:TAG]

其中 REPOSITORY[:TAG] 为唯一必填的位置参数,指向目标 OCI 仓库及标签(如 myorg/myapp:1.0.0),CLI 要求精确一个位置参数(cmd/compose/publish.go)。

这条命令的核心价值在于:它发布的不是容器镜像,而是“Compose 项目”本身——Compose 文件作为 OCI 制品的图层被推送到任意 OCI 兼容注册表,供其他用户以单一制品的形式拉取并运行你的多容器应用。

二、完整参数说明

命令参考文档 docs/reference/compose_alpha_publish.md 列出了以下参数,结合 cmd/compose/publish.go 中 flag 的注册代码,完整参数表如下:

名称 类型 默认值 说明
--dry-run bool 以 dry-run 模式执行命令(只演练,不真正推送)
--oci-version string 指定 OCI 镜像/制品规范版本(默认自动判定)
--resolve-image-digests bool 将镜像 tag 固定(pin)为 digest
--with-env bool 在发布的 OCI 制品中包含环境变量文件
-y, --yes bool 对所有交互提示自动回答 "yes"
--app bool 发布完整 Compose 应用(包含所引用的镜像,见下文第五节)
--insecure-registry bool 使用不安全的明文 HTTP 注册表(隐藏 flag,仅供测试)

几个源码层面的补充细节:

  • --y 已废弃:代码中的 flag 归一化函数会拦截 --y 并打印警告 --y is deprecated, please use --yes insteadcmd/compose/publish.go)。
  • --app 会隐含启用 digest 解析runPublish 中将 ResolveImageDigests 设置为 opts.resolveImageDigests || opts.appcmd/compose/publish.go),即发布完整应用时必然携带 digest 锁定层。
  • --insecure-registry 是隐藏 flag:源码注释明确说明“仅供测试用途,我们不鼓励使用不安全注册表”,因此官方参考文档中不展示它。
  • 命令在加载项目时会拒绝包含本地 include 的 Compose 文件,直接报错 cannot publish compose file with local includescmd/compose/publish.go)。

三、发布主流程

publish 的 API 入口是 pkg/api/api.go 中定义的 PublishOptions

// PublishOptions group options of the Publish API
type PublishOptions struct {
    ResolveImageDigests bool
    Application         bool
    WithEnvironment     bool
    OCIVersion          OCIVersion
    // Use plain HTTP to access registry. Should only be used for testing purpose
    InsecureRegistry bool
}

核心实现 composeService.publish 按以下顺序执行:

  1. 加载全量 Profileproject.WithProfiles([]string{"*"}) 确保带 Profile 的服务也一并被发布;
  2. 安全预检(preChecks):逐项检查并交互确认,任何一项拒绝都会以 api.ErrCanceled 终止(详见第四节);
  3. 推送被引用的镜像:调用内部 s.push(...),选项为 IgnoreFailures: true, ImageMandatory: true——先把 Compose 文件引用的镜像推送到注册表,保证使用者拉取制品后能直接运行;
  4. 构建 OCI 图层createLayers 将 Compose 文件、extends 父文件、env 文件、digest 锁定文件转换为描述符集合(详见第五节);
  5. 推送制品清单pushComposeArtifact 通过 oci.PushManifest 将空 config、各图层和 manifest 依次推送到注册表;
  6. (仅 --app)推送应用镜像索引:见第六节。

整个过程包裹在名为 "publish" 的事件 bracket 中(Run(ctx, ..., "publish", s.events)),进度通过 s.events.On(...)publishing / published 状态输出,--dry-run 模式下只走到图层构建为止,不会执行第 5 步。

四、发布前安全预检:防止泄露敏感信息

preChecks 是 publish 命令中交互逻辑最密集的部分,共包含四类检查,按顺序执行:

4.1 拒绝纯 build 服务

checkOnlyBuildSection 检查是否存在“只有 build 段而没有 image 字段”的服务。若存在,直接报错并列出服务名:

your Compose stack cannot be published as it only contains a build section for service(s):
- "serviceA"

因为发布的是项目定义而非构建上下文,发布者需要预先完成镜像构建与推送。

4.2 bind mount 声明确认

checkForBindMount 收集所有服务的 type: bind 卷声明并逐条列出,提示用户“只发布声明(declaration),不发布内容”,确认未挂载到敏感的用户目录后才继续(pkg/compose/publish.go)。

4.3 敏感数据扫描

checkForSensitiveData 使用 DefangLabs 的 secret-detector(scanner.NewDefaultScanner)扫描四类来源(pkg/compose/publish.go):

  • 所有 Compose 文件(以未插值状态加载后序列化扫描);
  • 各服务的 env_file 文件;
  • 以文件定义的 configs
  • 以文件定义的 secrets

命中(如 AWS_SECRET_ACCESS_KEY=...)时会逐项列出类型、键与值并要求确认;用户拒绝则中止。单元测试 Test_preChecks_sensitive_data_detected_decline 验证了拒绝即返回 accept=false 的行为。

4.4 环境变量相关发现(env findings)

checkEnvironmentVariablescollectEnvCheckFindingspkg/compose/publish.go)负责两类与 --with-env 相关的确认:

  1. env_file 声明疑似敏感字面量:服务中声明了 env_file、或环境变量的键名形如密码/密钥类(password、secret、token、api_key 等,由 keyword detector 判定)且值为字面量时,会聚合成一条提示,例如:

    you are about to publish env-related declarations within your OCI artifact.
    ...
      service "db": literal value for "MYSQL_ROOT_PASSWORD"
    Use --with-env to silence this prompt and always publish env declarations.
    Are you ok to publish these env declarations?
    

    关键点:插值值不会触发提示。形如 "${DB_PASSWORD}""$VAR" 的值在发布产物中保持符号占位符,检测器的值正则会自动跳过;而 $$ 转义出的字面 $(如 pa$$word)会被替换占位后正常判定,因此仍会被标记(对应测试见 pkg/compose/publish_test.go)。加上 --with-env 可静默此类提示。

  2. config 内联 content 字面量config.content 写的是字面内容(而非 ${VAR} 插值)时始终单独提示——它与 --with-env 解耦,因为该 flag 的语义只覆盖环境变量(见 envCheckFindings 的注释)。

以上行为的端到端验证在 pkg/e2e/publish_test.go:拒绝 env 提示时以退出码 130 中止且输出 test/test published 前不出现发布完成字样;接受 bind mount 提示后正常发布。

五、图层结构:Compose 文件如何变成 OCI layer

createLayers 决定最终制品里有哪些层,各层的 media type 定义在 internal/oci/push.go

常量 用途
ComposeYAMLMediaType application/vnd.docker.compose.file+yaml 每个 Compose 文件一层
ComposeEnvFileMediaType application/vnd.docker.compose.envfile 每个 env 文件一层(仅 --with-env
ComposeEmptyConfigMediaType application/vnd.docker.compose.config.empty.v1+json OCI 1.0 模式的 config 描述符
ComposeProjectArtifactType application/vnd.docker.compose.project OCI 1.1 模式的 artifactType

路径脱敏是图层构建的核心设计。processFile 对每个 Compose 文件做两件事:

  • 服务引用的 env_file 路径被重写为 sha256(路径).env 的无含义占位符(如 5efca9cdbac9...51af3.env);
  • extends.file 引用的父文件同样重写为 sha256(路径).yaml,并作为独立图层递归发布,该层额外标注 com.docker.compose.extends: "true"processExtends)。

重写通过 transform.ReplaceEnvFile / ReplaceExtendsFile 在 YAML 流上按行/列定位替换,保留原文件的格式与注释。这样做的目的是:发布产物永远不会泄露发布者机器的本地路径,且哈希由路径字符串本身推导,即使可选 env 文件缺失(required: false),占位符也保持一致(测试 Test_processFile_optional_env_file_missing)。

单元测试 Test_createLayers 基于 pkg/compose/testdata/publish/ 下的样例(compose.yaml + common.yaml + test.env)断言了完整结果:发布后的 YAML 中 extends.file 变成 f8f9ede3...90c.yaml,三种 env_file 写法(字符串、列表、映射)统一替换为 5efca9cd...451af3.env,并生成 3 个图层(主文件、extends 父文件、env 文件)。

每个文件层的描述符还携带两个注解:com.docker.compose.file(或 com.docker.compose.envfile)标注文件基名,com.docker.compose.version 标注生成它的 Compose CLI 版本(DescriptorForComposeFile)。

--resolve-image-digests 的 digest 锁定层:开启后,generateImageDigestsOverride 通过 WithImagesResolved 对每个服务镜像(含 pre_start hook 镜像、type: image 卷源镜像)执行 DistributionInspect 解析出 digest,生成一份形如:

services:
  app:
    image: docker.io/library/nginx:latest@sha256:aaaa...

image-digests.yaml 覆盖层追加到制品中,消费方据此即可把浮动 tag 固定到不可变 digest。测试 Test_generateImageDigestsOverride_resolvesDependentImages 验证了依赖镜像(hook、image volume)都会被逐一解析。

六、OCI 清单生成与注册表兼容

PushManifest 实现了文档中 --oci-version “automatically determined by default” 的自动判定逻辑:

  1. 版本为 1.1 或未指定时,先推送一个空 JSON config blob;
  2. 依次推送所有图层;
  3. 未显式指定版本时,优先按 OCI 1.1 生成 manifestArtifactType: application/vnd.docker.compose.project);若收到非认证类的 4xx 错误(推断注册表不支持 OCI 1.1),自动回退到 OCI 1.0
  4. 认证类错误(401/403/407,见 clientAuthStatusCodes)不会触发回退,直接报错。

两个版本的关键差异在 generateManifest

  • OCI 1.1:config 为空描述符,artifactType 字段声明 application/vnd.docker.compose.project
  • OCI 1.0:spec 无 artifactType 字段,按 OCI 1.1 规范的回退建议改用 config.mediaType 标识——这里使用专属的 application/vnd.docker.compose.config.empty.v1+json(内容为 {}),让工具能把它识别为 Compose 制品而非容器镜像。

所有 manifest 均带 org.opencontainers.image.created 时间戳注解。仓库凭证方面,oci.NewResolver 从 Docker CLI 的登录配置(config.GetAuthConfig,支持 IdentityToken)取凭据,并复用 Docker Desktop 的代理 transport 访问注册表,因此 docker login 过的用户无需额外配置。

七、--app:发布可整体拉取的完整应用

--app 模式下,pushComposeArtifact 额外调用 pushApplicationIndex

  • 遍历项目的每个服务,将其镜像通过 oci.Copy 以 registry mount 的方式“拷贝”到目标仓库(利用 LabelDistributionSource 注解触发跨仓库 mount,避免重复上传);
  • 生成一个 MediaTypeImageIndex 索引,Manifests 引用所有服务镜像,Subject 指向此前发布的 Compose 制品 manifest,ArtifactType 同为 application/vnd.docker.compose.project
  • 如前所述,该模式同时隐含 ResolveImageDigests,因此制品中始终带有 digest 锁定层。

这样消费方可以用单一引用(索引 digest)拉到“Compose 定义 + 全部镜像”的完整应用。

八、典型使用方式与注意事项

一次完整的发布命令通常长这样:

# 交互式发布(会逐项确认 bind mount / 敏感数据 / env 声明)
docker compose alpha publish myorg/myapp:1.0.0

# CI/脚本化发布:自动应答 + 包含 env 文件 + 固定镜像 digest
docker compose alpha publish --with-env --resolve-image-digests -y myorg/myapp:1.0.0

# 发布含全部镜像的完整应用
docker compose alpha publish --app -y myorg/myapp:1.0.0

# 先演练不推送
docker compose alpha publish --dry-run myorg/myapp:1.0.0

结合源码可确认的使用约束:

  1. 服务必须带 image 字段,纯 build 服务会被整体拒绝,需先构建并推送镜像;
  2. 不允许本地 include,多文件请通过 extends 组织(extends 父文件会随制品一起发布);
  3. -y 会跳过所有确认(包括敏感数据确认),在 CI 中应确保 Compose 文件中没有字面量密钥;
  4. 插值占位符(${VAR})会被原样发布,消费方需自行提供对应环境变量——这正是 --with-env 与符号插值之间的取舍点。

九、相关代码与测试索引

关注点 位置
命令与 flag 定义 cmd/compose/publish.go
发布主流程、图层构建、预检 pkg/compose/publish.go
OCI media type 与 manifest 生成 internal/oci/push.go
注册表解析器与镜像 Copy internal/oci/resolver.go
YAML 路径重写(保格式) pkg/compose/transform/replace.go
单元测试(图层/预检/digest 解析) pkg/compose/publish_test.go
端到端场景测试 pkg/e2e/publish_test.go
样例数据 pkg/compose/testdata/publish/compose.yaml
命令参考文档 docs/reference/compose_alpha_publish.md
登录后查看全文
热门项目推荐
相关项目推荐