容器清单导出来了,然后呢?反向生成 compose 的四条路和一条铁律
文章目录 介绍 演示环境 反向生成 compose四条路一条铁律 先想清楚反向生成的目标是「对得上」不是「跑起来」⚖️ 第一条路现成反推工具 第二条路手工对照清单敲 compose 第三条路AI 辅助生成把清单喂给模型 第四条路compose 原生反向docker compose config 四条路摆在一起✅ 终验新文件必须回到 inspect 对一遍 反向生成里最容易踩的三个坑 反向生成之后让文件成为唯一真源⚓ 相关链接 ⚓ 介绍 上篇写了怎么把容器的现状问清楚——用一段 inspect 脚本把镜像、环境变量、端口、挂载、重启策略导成一份能照抄的清单。后台有读者问得很直接清单在手然后呢「然后」就是剩下的那个问题怎么把这份清单反向变成一份可靠的 compose 文件。听着像体力活实际路上的岔口比想象多——有现成工具帮你反推有手工对照着敲还有中间那些看起来能省事、实际上会把坑埋得更深的捷径。下面四条路各走一遍每条给出适用场景、真实开销和它各自会咬人的地方最后汇总成一条动手前必须记住的铁律。 演示环境 运行平台飞牛 NASfnOSDocker Engine 28.xDocker Compose v2 容器规模20 个左右compose 管理与裸容器混跑 前置产出上篇的 inventory.sh 清单含镜像/环境变量/端口/挂载/网络/重启策略 网络全内网操作不涉及公网注意本文内容为个人学习笔记仅供参考。附读者须知 反向生成 compose四条路一条铁律 先想清楚反向生成的目标是「对得上」不是「跑起来」这步想偏了后面全白费。反向生成的目标不是「让容器跑起来」——那太容易了docker run一敲就跑。真正的目标是让新文件描述的状态与线上容器的实际状态逐项对得上包括那些没人写进过任何文件的变更。上篇强调过一个事实compose 文件记录的是创建那一刻的意图容器跑起来之后的变更走的是另一条路。所以反向生成时你的输入不是「旧 compose 文件」而是线上容器的实际状态。这条铁律贯穿全文对照的基准是 inspect 输出不是任何一份旧文件。新文件生成后必须回到 inspect 逐项核对。⚖️ 第一条路现成反推工具社区里有专门干这件事的工具思路是读容器对象自动吐出等价的docker run命令或 compose 片段。runlike是用得最多的一个用法是把它作为一个一次性容器跑起来指向目标容器dockerrun--rm-v/var/run/docker.sock:/var/run/docker.sock\assaflavie/runlike容器名它会输出一条完整的docker run命令把端口、挂载、环境变量、重启策略都还原出来。对单容器快速迁移这条路的效率最高。它有几处会咬人一是环境变量会被原样还原——包括镜像自带的PATH这种噪音也包括那些当初经由部署面板注入、你早忘了来路的值。输出的命令行可能比你想的长得多逐项过一遍是必须的。二是它只看容器对象不会区分「Compose 管理的孩子」和裸容器。对 compose 容器跑 runlike得到的是一条巨大的docker run而你本来可以只维护一份十几行的 compose 文件。所以它更适合裸容器。三是挂载路径会按容器视角还原成绝对路径这在单机迁移时没问题但坑③那类「路径视角错位」的场景里宿主机路径要人工再核一遍。适用场景单容器、裸容器、快速搬迁。不适合 compose 项目批量反推。 第二条路手工对照清单敲 compose上篇的 inventory.sh 清单本身就按 compose 的字段顺序组织镜像 → 命令 → 环境变量 → 端口 → 挂载 → 重启策略 → 网络 → 日志。手工翻译就是把清单的每一行映射到 compose 键services:服务名:image:镜像名:标签container_name:容器名# 有固定名字的才写compose 默认会给项目前缀名restart:unless-stopped# 对应 RestartPolicy.Nameenvironment:-APP_DOMAINhttps://你的域名-SIGNUPS_ALLOWEDfalse-TZAsia/Shanghai# 不要照抄镜像里的 PATHports:-8080:80# 对应 PortBindings注意 HostIp 空串 全网卡volumes:-/数据盘/docker/服务名/data:/data# bind 挂载宿主路径 - 容器路径networks:-网络名# 对应 NetworkSettings.Networkslogging:driver:json-fileoptions:max-file:5max-size:100m# 对应 LogConfig手工路的好处是每一行都是你敲的——哪个字段干什么、为什么这么填过程里自然就懂了。对只有三五个容器的家庭 NAS这条路的总开销其实最低。它会咬人的地方敲漏。清单十几行compose 五十行中间的映射全靠注意力。特别是那些「不在清单上但要单独核」的字段——上篇列过的网络别名、健康检查、标签手敲时最容易忘。所以手工路必须配一个动作敲完回头跟 inspect 输出 diff 一遍方法见后文验证节。 第三条路AI 辅助生成把清单喂给模型把 inventory 清单直接贴给 AI 助手让它翻译成 compose。这条路的真实效率比预想的高但有讲究。给什么完整的 inspect JSON或规整后的清单、目标 compose 版本v2 语法、以及一句明确的要求——「逐字段映射不确定的字段标出来问我不要编」。这句话很重要模型对不确定的字段会倾向于「填一个看起来合理的」而 compose 里一个看起来合理的默认值上线后就是一次意外。验什么AI 生成的 compose 必须走和其他路完全一样的验证流程而且要多验一项——它有没有把你没给的信息编进去。常见编造包括给没配健康检查的容器加一个healthcheck、把重启策略从unless-stopped自作主张改成always、给端口加上协议字段。这些「看起来更专业」的补充全是与线上实际状态不符的偏差。适用场景容器多、字段杂、有时间压力。但前提是你有能力审它的输出——审不出来就别用这条路错误会藏在格式漂亮的 YAML 里。 第四条路compose 原生反向docker compose config如果容器本来就是 compose 管的还有一条捷径找到它的编排文件上篇说过怎么找project标签里带路径再配合docker compose config把运行时的合并结果打出来cd编排文件所在目录dockercompose config它会展开变量替换、合并 override 文件、补上默认值——输出的是「当初部署时实际生效的完整配置」。这对「文件在但被 override 魔改过」的场景特别有用。它的局限输出的是「创建那一刻的意图 变量展开」不包含容器跑起来之后的变更docker update改的内存、exec 装的包。所以对 compose 容器正确姿势是「第四条路打底 inspect 逐项核对找差异」而不是直接拿 config 输出当最终答案。适用场景compose 容器、需要理解原始部署意图、批量梳理。 四条路摆在一起维度一 现成工具二 手工对照三 AI 辅助四 compose 原生适用对象裸容器 / 单容器少量容器批量 / 字段杂compose 容器速度快慢快快准确性高容器视角取决于细心必须审核高但只到创建时点学习价值低高中中主要风险噪音变量 / 路径视角敲漏字段编造合理默认值漏掉运行期变更组合建议—三五容器有审核能力时先打底再核对推荐组合compose 容器用「四打底 inspect 核差异」裸容器少就手工敲顺便学一遍多就用 runlike 或 AI 辅助但必须逐字段审核。任何一条路生成的新文件都要过同一道终验。✅ 终验新文件必须回到 inspect 对一遍不管走哪条路最后一步都一样用新文件在测试环境起一个容器再拿它的 inspect 输出和线上容器比对。肉眼对 YAML 是不牢靠的——缩进、引号、列表语法都可能让一个字段悄悄失效。最小验证脚本把两边的关键字段并排打出来#!/usr/bin/env bash# 用法./diff-inspect.sh 线上容器名 新起的容器名A${1:?线上容器}B${2:?新容器}forcin$A$B;doecho$cdockerinspect$c--format image{{.Config.Image}} restart{{.HostConfig.RestartPolicy.Name}} env{{range .Config.Env}}{{.}};{{end}} ports{{json .HostConfig.PortBindings}} mounts{{range .Mounts}}{{.Source}}-{{.Destination}};{{end}} net{{range $k, $v : .NetworkSettings.Networks}}{{$k}};{{end}} log{{json .HostConfig.LogConfig.Config}}done两份输出并排看。期望是逐行相同有差异就回 compose 文件改再起再比。特别留意三处环境变量的顺序无关紧要但内容不能少挂载的宿主路径必须是真实存在且有权写入的网络别名在 inspect 的 Networks 段里。验证通过后还有一步容易漏的把数据搬过去。bind 挂载的数据目录直接 rsync命名卷用docker run --rm -v 卷:/from -v 新卷:/to alpine cp -a /from/. /to/这类方式过一遍。新文件 空数据目录 服务起来但一片空白这种「半成品状态」比服务挂了更耽误事。 反向生成里最容易踩的三个坑坑一把「能跑」当「迁移完成」。新容器起来了、页面能打开就容易收工。但定时任务、cron 作业、数据库连接串里写死的地址——这些不在 inspect 清单里inspect 只看容器对象看不到容器内部在跑什么要在应用层单独确认。坑二compose 项目名变了。compose 的网络名和卷名默认带项目前缀项目名_网络名。换目录部署时项目名变了旧容器引用的旧网络名就成了孤儿。稳定做法compose 文件里显式写name: 原项目名或网络/卷都用external: true引用已有资源。坑三忽略时区。上篇说过容器默认 UTC反向生成时新文件如果忘了TZ环境变量日志时间又慢八小时对账时又是一轮怀疑错方向。清单里有TZAsia/Shanghai就必须在新文件里看到它。 反向生成之后让文件成为唯一真源迁移完成不等于事情结束。反向生成的 compose 文件价值在于从今往后它就是唯一真源——所有变更改文件、up -d应用别再docker exec或docker update直改容器。上篇说的「文件和现实的差距会像滚雪球」反向生成就是把雪球清零的动作之后的日常维护决定它会不会再滚起来。一个低成本的习惯每次up -d后顺手跑一遍那个 diff-inspect 脚本确认现实仍与文件一致。三十秒的事能让你永远不需要再做一次「反向生成」。⚠说明本文基于 Docker Engine 28.x 与 Docker Compose v2 的实测、2026 年 10 月的官方文档整理。Docker 与 Compose 的命令行为、字段名、第三方工具如 runlike的能力在版本间可能调整请前往官方仓库与文档查看最新内容并自行核实一切以官网当前版本为准本文所述仅代表整理时点的整理情况仅供参考。⚓ 相关链接 ⚓XiaoqiangClubNAS 上的容器要搬去新机器先把它现在的样子问清楚本系列前作Docker 官方文档 · Compose 文件参考Docker 官方文档 · docker compose configrunlike 项目容器反推 docker run 命令