Docker Compose publish 命令详解:把 Compose 应用发布为 OCI 工件
docker compose publish 用于将一个 Compose 项目整体发布到 OCI 兼容的镜像仓库:命令会先对 Compose 文件做安全性预检(敏感数据、环境变量、bind mount 声明),再把 Compose 文件及其引用的 env 文件、extends 父文件序列化为一组 OCI 层,推送到目标仓库,生成一个以 application/vnd.docker.compose.project 为 artifact type 的 OCI 工件。读完本文,你将掌握 publish 各参数的含义与使用前提、工件在仓库中的实际结构,以及如何用 docker compose -f oci://... 拉回并发布(pull 后 up)这个工件。
命令与参数速览
命令语法为:
docker compose publish [OPTIONS] REPOSITORY[:TAG]
其中唯一的必填位置参数是目标仓库引用(REPOSITORY[:TAG]),必须已具备该仓库的推送权限。参数一览(来自 docs/reference/compose_publish.md,并结合 cmd/compose/publish.go 中的 flag 定义补充):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--app |
bool |
false |
发布完整应用工件(额外附带一个引用全部服务镜像的 OCI image index) |
--dry-run |
bool |
false |
以 dry run 模式执行,只走加载、预检与序列化流程,不实际推送 |
--oci-version |
string |
自动判定 | 指定生成的 OCI image/artifact 规范版本,默认自动判定 |
--resolve-image-digests |
bool |
false |
将镜像 tag 解析并固定为 digest |
--with-env |
bool |
false |
将环境变量文件(env file)内容一并写入发布的 OCI 工件,并静默 env 相关确认提示 |
-y, --yes |
bool |
false |
对所有确认提示自动回答 "yes" |
--insecure-registry |
bool |
false |
以明文 HTTP 访问仓库(隐藏参数,源码注释标明仅供测试用途) |
几个源码中可以确认的实现细节:
--app打开时,runPublish会强制ResolveImageDigests: opts.resolveImageDigests || opts.app(见 cmd/compose/publish.go),即发布完整应用时镜像引用必然固定为 digest,保证 index 指向的镜像不可漂移。-y会把AlwaysOkPrompt()注入服务选项(cmd/compose/publish.go),跳过 bind mount、敏感数据、env 等全部交互确认;源码还保留了对误写的--y的向后兼容与弃用警告(cmd/compose/publish.go)。--insecure-registry在 flag 注册后被MarkHidden,注释明确写着 "Should only be used for testing purpose, we don't want to promote use of insecure registries"(cmd/compose/publish.go)。- 仓库加载阶段如果检测到本地
include指令,runPublish会直接报错cannot publish compose file with local includes(cmd/compose/publish.go),因为 include 内容无法作为独立工件分发。
发布前预检:四类安全检查
publish 的入口实现是 pkg/compose/publish.go 中的 Publish/publish 方法。执行推送前,preChecks 会依次做四件事(pkg/compose/publish.go):
1. 拒绝纯 build 服务
checkOnlyBuildSection 会检查每个服务:如果 Image 为空且只有 build 段,整个项目无法发布,直接报错并列出这些服务名:
your Compose stack cannot be published as it only contains a build section for service(s):
- "serviceA"
原因很直接:发布工件只固化 image 引用,消费者无法在无构建上下文的环境中重现构建。
2. bind mount 声明确认
checkForBindMount 收集所有服务的 bind 类型卷(pkg/compose/publish.go),若存在则交互式提示:
you are about to publish bind mounts declaration within your OCI artifact.
only the bind mount declarations will be added to the OCI artifact (not content)
please double check that you are not mounting potential user's sensitive directories or data
Are you ok to publish these bind mount declarations?
注意只发布声明而不发布内容——即消费者的 /host/path 会映射到他自己的宿主路径,提示的目的是防止把含敏感路径的声明扩散出去。
3. 敏感数据扫描
checkForSensitiveData 使用 DefangLabs 的 secret-detector 扫描四类内容(pkg/compose/publish.go):
- 所有 Compose 文件(以未插值的原始 YAML 重新序列化后扫描,检查用户真实写入的字面量);
- 各服务声明的 env 文件(文件缺失时,仅
required: true才报错,可选 env 文件缺失则跳过); configs:中由file:定义的资源;secrets:中由file:定义的资源。
命中后逐项列出类型与键值并要求确认,例如 e2e 测试 pkg/e2e/publish_test.go 验证的输出覆盖了 AWS Client ID、AWS Secret Key、Github authentication、JSON Web Token、Private Key 等类别。拒绝(输入 n)会使整个发布以 operation cancelled(退出码 130)终止,--yes 则全部自动确认。
4. 环境变量与 config content 泄漏检查
checkEnvironmentVariables 针对"即将被序列化进工件的每一个 Compose 文件"(含 extends 父文件)做更细粒度的 env 检查(pkg/compose/publish.go):
- env_file 声明:任何服务声明了
env_file都会列出(service "X": env_file declared),因为文件路径本身即本地环境信息; - 可疑键名字面量:用关键字检测器(password、secret、token、api_key 等键名)扫描
environment的字面值,如MYSQL_ROOT_PASSWORD: toto会被标记为service "db": literal value for "MYSQL_ROOT_PASSWORD"; - 插值安全:
"${DB_PASSWORD}"、"$API_KEY"这类插值在工件中保持符号形态、不泄漏解析后的值,因此不会触发提示(pkg/compose/publish.go 的注释与 pkg/compose/publish_test.go 中 "interpolated values are silent" 用例均确认了这一点);$$转义(字面$)会被还原判断,"pa$$word"这类仍按字面量告警; - 字面
config.content:内联 config 内容若为字面量(非插值模板),单独弹出一个确认框,该检查与--with-env解耦——--with-env只控制环境变量相关提示。
提示文案中还写明了出口:"Use --with-env to silence this prompt and always publish env declarations.",这些行为由 pkg/e2e/publish_test.go 的场景测试逐一固化。
工件内容:层是如何生成的
预检通过后,publish 会调用非导出的 s.push(而非公开 Push)先把项目引用的镜像推送到仓库(IgnoreFailures: true, ImageMandatory: true,pkg/compose/publish.go)——即每个服务声明的 image 必须可推送成功,这是 ImageMandatory 的语义。
随后 createLayers 构建工件的层(pkg/compose/publish.go):
- Compose 文件层:每个顶层 Compose 文件经过
processFile处理——以原始环境重新加载后做两项改写:env_file路径被替换为路径字符串 SHA-256 哈希命名的<hash>.env占位符,真实文件仅在--with-env时作为独立层上传(media typeapplication/vnd.docker.compose.envfile)。文件缺失但required: false时,占位符仍写入(保证工件不泄漏本地路径且行为一致),只是不注册上传,这一点由 pkg/compose/publish_test.go 的Test_processFile_optional_env_file_missing验证;extends.file同样被改写为<hash>.yaml占位符,父文件作为带com.docker.compose.extends: "true"注解的层递归上传(pkg/compose/publish.go)。因此工件中不存在发布者的绝对/相对路径。
- env 文件层(仅
--with-env):由envFileLayers把envFilesmap 中登记的文件逐一打包(pkg/compose/publish.go)。 - digest override 层(仅
--resolve-image-digests或--app):generateImageDigestsOverride通过ImageDigestResolver对服务镜像、pre_starthook 镜像、type: image卷源镜像逐一DistributionInspect,生成一份只含image: repo@sha256:...的 override YAML(pkg/compose/publish.go,覆盖范围由 pkg/compose/publish_test.go 的 mock 测试确认)。
单元测试 Test_createLayers(pkg/compose/publish_test.go)展示了改写后的实际形态:extends.file 变为 f8f9ede3...d390c.yaml,env_file 变为 5efca9cd...451af3.env,且各层带有 com.docker.compose.file / com.docker.compose.envfile 注解。测试数据可参考 pkg/compose/testdata/publish/compose.yaml。
OCI 工件结构与版本兼容
层与清单的推送在 internal/oci/push.go 中完成。工件的关键常量:
| 常量 | 值 | 用途 |
|---|---|---|
ComposeProjectArtifactType |
application/vnd.docker.compose.project |
OCI 1.1 manifest 的 artifactType 字段 |
ComposeYAMLMediaType |
application/vnd.docker.compose.file+yaml |
Compose 文件层的 media type |
ComposeEmptyConfigMediaType |
application/vnd.docker.compose.config.empty.v1+json |
OCI 1.0 模式下的空 config media type |
ComposeEnvFileMediaType |
application/vnd.docker.compose.envfile |
env 文件层的 media type |
PushManifest 的版本策略(internal/oci/push.go):
- 未指定
--oci-version时,先尝试按 OCI 1.1 生成清单(带artifactType、空 JSON config); - 若仓库返回非认证类的 4xx(说明仓库不支持 1.1 特性),自动回退为 OCI 1.0 形态:
artifactType省略,config 使用ComposeEmptyConfigMediaType,让依赖 config.mediaType 识别工件的旧工具链仍能认出这是 Compose 工件(internal/oci/push.go); --oci-version显式取值1.0/1.1(pkg/api/api.go)时跳过探测。API 注释还说明:Compose 目前会基于域名对 ECR 仓库自动使用 OCI 1.0 模式(pkg/api/api.go)。
--app:可整体拉取的应用 index
--app 会额外推送一个 image index(pkg/compose/publish.go):
- 对每个服务的 image 执行 registry-to-registry 复制引用(
oci.Copy),把所有服务镜像聚合成一个MediaTypeImageIndex; - 该 index 的
Subject指向 Compose 项目 manifest,artifactType同样为application/vnd.docker.compose.project,并带com.docker.compose.version注解; - 由于强制 resolve digests,消费者拉取 index 时拿到的镜像内容与发布时刻完全一致。
这样"应用 = Compose 定义 + 全部镜像"成为一个仓库内自包含的整体。
端到端验证:发布后如何消费
e2e 测试 TestPublish(pkg/e2e/publish_test.go)给出了完整的发布—消费闭环,可以直接照搬到本地实操:
# 1. 起一个本地 registry:3
docker run --name reg -P -d registry:3
# 2. 发布 Compose 项目(fixture 为 compose.yaml + compose-override.yaml)
docker compose -f compose.yaml -f compose-override.yaml \
-p myapp publish --with-env --yes <registry-host:port>/test:test
# 3. 用 oci:// URI 直接以该工件为 compose 文件查看解析结果
docker compose -f oci://<registry-host:port>/test:test config
# 4. 甚至可以直接 up
docker compose -f oci://<registry-host:port>/test:test up
测试断言了 config 输出与原始 Compose 语义一致(服务环境、镜像、网络),并特别覆盖了一个回归场景:up 会二次加载远程工件来枚举待插值变量,此时必须继承 --insecure-registry(对应 pkg/e2e/publish_test.go 中对 docker/compose#13824 的注释)。发布用的 fixture 可参考 pkg/e2e/fixtures/publish/oci/compose.yaml(其中 extends.yaml 作为 extends 父文件同目录存放)。
各预检路径的 e2e 断言同样可查:bind mount 提示接受/拒绝(pkg/e2e/publish_test.go)、纯 build 项目被拒(pkg/e2e/publish_test.go)、本地 include 被拒(pkg/e2e/publish_test.go)、敏感数据全类别列出(pkg/e2e/publish_test.go)。
使用前提与注意事项小结
- 适用版本前提:以当前仓库
github.com/docker/compose/v5的源码为准;--app、--with-env、digest 固定等行为均属该版本实现,旧版 CLI 行为可能不同。 - 硬性限制:含本地
include的项目、纯build无image的服务无法发布,命令会直接报错而非进入交互。 - 安全默认值:默认不发布 env 文件内容;env_file 声明、可疑键名环境字面量、字面
config.content、bind mount 声明、扫出的密钥都会触发交互确认,-y会跳过所有确认,CI 场景下请先确认声明内容确实可公开。 - 仓库兼容性:绝大多数场景无需关心
--oci-version,自动"1.1 优先、4xx 回退 1.0"已覆盖主流仓库;仅当目标仓库行为异常或需要复现工件形态时才显式指定。 - 路径安全设计:发布的工件中不含发布者本地路径——env 文件与 extends 父文件一律以路径哈希占位符引用(pkg/compose/publish.go),这是消费端可以安全复用该工件的前提。
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