--- name: runtime-image description: 在具体项目中探测技术栈并按 runtime-base 镜像矩阵选择正确的基础运行时镜像、生成/修正项目 Dockerfile;也能对已存在的 Dockerfile 做评估审计并修正。凡用户要求「写 Dockerfile / 容器化这个项目 / 选基础镜像 / FROM 哪个镜像 / 帮我打包成镜像 / 这个项目部署成容器 / 评估一下这个 Dockerfile / 帮我看看 Dockerfile 有没有问题 / 优化 / 评审 / 精简镜像」,或出现 容器起不来 / exec format error / 镜像太大 / pip 安装编译失败 / latest 镜像行为变了 等症状,或提到 runtime-node / runtime-pm2 / runtime-base / livebook:8418 / code.innev.cn / 镜像源 时,务必使用本 skill——即使用户没说「运行时镜像」四个字。Use whenever the user needs to pick a base runtime image (runtime- matrix), write a project Dockerfile, or audit/fix an existing Dockerfile. 边界:构建/推送业务镜像归项目 CI 或 xgent-image-push;修改 runtime 镜像本身(加镜像/改版本)归 runtime-base 仓库维护,不用本 skill。 --- # runtime-image — 按项目语言与环境选运行时镜像 依据 runtime-base 仓库的镜像矩阵(10 个镜像:Linux 7 个 Alpine 系 + Wine 3 个),在项目里完成「探测栈 → 选型 → 写 Dockerfile → 真机验证」。两种工作模式: - **新建模式**:项目没有 Dockerfile,从零选型生成 - **评估修正模式**:项目已有 Dockerfile(或用户要求"看看这个 Dockerfile"),先审计再修正 核心理念(推理链,所有约束都挂在这上面):**runtime 系列只负责把编译好的代码跑起来**——构建/编译由项目自己的 CI 完成,镜像内无编译器/SDK(Node 系唯一例外:容器启动时才 install+build,自带 node-gyp 链)。因此项目 Dockerfile 恒为单段:`FROM runtime-` → `COPY 产物` → `CMD`。 ## 总流程 ``` 前置 镜像源探测(脚本自动判内网/外网) → 第 0 步 探测技术栈(同时探测已有 Dockerfile → 有则转评估修正模式) → 第 1 步 选型对照 → 第 2 步 定 tag → 第 3 步 写/改 Dockerfile → 第 4 步 真机验证 → 第 5 步 汇报 ``` ## 前置:镜像源探测(每次任务最先做,绝不可跳过) 镜像源有两路,**由探测决定,不许凭经验猜**: | 源 | 前缀 | 适用 | 条件 | |---|---|---|---| | 内网 | `livebook:8418/images` | 内网机器,构建拉取最快 | ① daemon.json 已登记 `insecure-registries: ["livebook:8418"]`(明文 HTTP,不登记 docker 拒拉)② `http://livebook:8418/v2/` 可达(200/401 均算通) | | 外网 | `code.innev.cn/images` | 外网/分发给同事使用 | 内网任一条件不满足时兜底 | 执行 `scripts/detect-registry.sh`,stdout 最后一行即镜像前缀;之后所有 Dockerfile 的 `FROM` 统一写成 `<前缀>/runtime-:`。两路都不通(exit 1)→ die:停下报排查方向(内网机器确认同网段+登记+重启 docker;外网机器确认能访问 `https://code.innev.cn`),不硬写任何一个。 → 验收:本次任务的镜像前缀有探测结论(内网/外网)背书,并记入第 5 步汇报。 ## 第 0 步:探测技术栈(绝不可跳过) 不许凭用户一句话定镜像——读项目文件坐实,再听用户补充部署需求。 | 探测到什么 | 判定 | |---|---| | `package.json`(无构建产物直出静态页) | Node 后端 → 再问:单进程还是多进程/需要 cluster? | | `package.json` + 纯前端(react/vue/vite 静态产物) | **不走本系列**,CI 出静态文件由项目自己的 nginx 镜像托管 | | `requirements.txt` / `pyproject.toml` | Python | | `go.mod` / `Cargo.toml` | Go/Rust → 静态二进制 → `runtime-base` | | `pom.xml` / `build.gradle` | Java → jar | | `*.csproj` / `*.sln` | .NET → **必须再问一句**:现代 .NET(publish 产物)还是 Framework 4.x?两者走完全不同的镜像 | | `CMakeLists` / `Makefile`(C/C++) | C/C++ → **必须确认 musl 动态链接**(见约束⑤) | | MQL4 / MT4 终端需求 | `runtime-mt4` | | 脚本/运维工具 | `runtime-base` | **同时探测已有 Dockerfile**:glob 项目内所有 `Dockerfile*`(含子目录、`.dockerignore`)。存在即转入「评估修正模式」(见第 2 步之后);不存在走新建模式顺次执行。多个 Dockerfile 逐一评估。 → 验收:判定结论有文件证据(路径),拿不准的在选型前问清,不猜;是否已有 Dockerfile 有明确结论。 ## 第 1 步:选型对照 | 项目类型 | FROM | 项目提供什么 | |---|---|---| | Node 后端(单进程) | `runtime-node` | 源码(**不含** node_modules);启动时 install+build | | Node 后端(多进程) | `runtime-pm2` | 同上 + pm2 cluster(缺省 max 核数) | | Python | `runtime-python` | 源码 + `venv/`(CI 装好依赖) | | Go / Rust 静态二进制 | `runtime-base` | 编译好的二进制 | | C/C++(musl 动态链接) | `runtime-cpp` | 编译好的二进制 | | Java jar | `runtime-java` | `target/*.jar` | | 现代 .NET publish 产物 | `runtime-dotnet` | `dotnet publish` 输出目录 | | .NET Framework 4.x | `runtime-wine-dotnet` | `bin/Release/` 目录 | | MT4 终端 | `runtime-mt4` | 不 COPY,运行时挂数据目录 | | 脚本/工具 | `runtime-base` | 脚本文件 | 镜像全名:`<前缀>/runtime-:`——前缀由前置探测给出(内网 `livebook:8418/images` / 外网 `code.innev.cn/images`),也可被 `INNEV_IMAGE_PREFIX` 显式覆盖(CI 场景)。矩阵详情(预装内容/环境变量/Wine 说明)读 `references/runtime-matrix.md`,按需读取。 die:探测结果不在矩阵内(如 Ruby/PHP)→ 停下报"无匹配运行时镜像",给两个去向(项目自建完整镜像 / 去 runtime-base 仓库加新镜像),**不硬塞一个近似的**。 → 验收:选出的镜像在对照表中有明确一行,且该行的特殊前提已逐条确认(musl 还是 glibc / 现代 .NET 还是 4.x / 单进程还是多进程)。 ## 第 2 步:定 tag 三类 tag:`latest`(每周一随上游滚动重建)/ `短sha` / `版本标签`(如 v0.5.0)。 - 生产/交付项目 → 锁 `版本标签` 或 `短sha`(warn:用户坚持用 latest 时提醒滚动重建风险,不拦) - 本地试跑 → latest 可接受 → 验收:Dockerfile 里 FROM 的 tag 是显式选择的结果,不是顺手写的。 ## 评估修正模式(第 0 步发现已有 Dockerfile 时走此分支) 先做完第 1、2 步得出"应该长什么样"(正确镜像 + tag),再拿现状逐项对照审计。**只读审计,审计报告出来之前不改任何文件**。 ### 审计清单(每条挂着硬约束编号,级别定义见下) | # | 检查项 | 违反 | 级别 | |---|---|---|---| | A1 | 多段构建 / 装了 SDK / 镜像内编译(`RUN mvn package`、`dotnet build`、`pip install` 源码编译) | ① | 必须修 | | A2 | Node 系 COPY 了 node_modules / 缺 `.dockerignore` 排除 / 把 dist 产物当通用模式 COPY(Node 系模型是启动时 install+build) | ② | 必须修 | | A3 | `ARG` 传令牌 / 密钥 COPY 进层 / `.npmrc` 含 token 落镜像层 | ③ | 必须修(安全项,优先处理) | | A4 | `FROM ...:latest` 且是生产/交付项目 | ④ | 建议修 | | A5 | glibc 环境(debian/ubuntu CI)构建的 C/C++ 产物 FROM alpine 系 | ⑤ | 必须修 | | A6 | Go/Rust 二进制 FROM `golang`/`rust` 全镜像或其他带语言环境的镜像 | ⑥ | 建议修(换 `runtime-base`,体积常减一个数量级) | | A7 | 前端静态产物用 node 镜像 `serve`/`http-server` 跑 | ⑦ | 建议修(换 nginx 托管) | | A8 | 栈与 FROM 错配(Framework 4.x 用 `runtime-dotnet`、多进程需求用 `runtime-node` 单进程跑等) | 选型表 | 必须修 | | A9 | 缺 `CMD`/`ENTRYPOINT`、`EXPOSE`、入口路径与 CI 产物路径对不上 | — | 提示 | | A10 | FROM 前缀与前置探测结论不符(外网机器 FROM `livebook:8418` 拉不动;内网机器 FROM `code.innev.cn` 白白绕远) | ⑧ | 必须修 | 级别定义:**必须修** = 会挂(起不来/行为错)或泄密;**建议修** = 能跑但体积/稳定性/可维护性受损;**提示** = 瑕疵。只发现"建议修/提示"且用户只要评估时,不改文件,出报告即止。 ### 审计报告格式(固定) - 结论一行:当前 FROM 什么 → 应该 FROM 什么(一致则明说"选型正确") - 问题表:级别 | 行号 | 问题 | 依据(约束编号) | 修法 - 没问题的项也要说一句"已过"(A1-A9 逐条过,不许只报坏不报好——漏报和误报一样有害) 报告摊开、用户确认要修 → 转第 3 步按模板改写(给 diff),再进第 4 步验证。用户说"只评估不动手"→ 报告即交付物。 ## 第 3 步:写/改 Dockerfile 默认动作:**先把选型结论 + 拟写的 Dockerfile 全文摊给用户确认,再落盘**(改已有 Dockerfile 时给 diff)。 模板(通用模式,python/java/dotnet/cpp/base/wine 同构): ```dockerfile FROM livebook:8418/images/runtime-: COPY <目标路径> CMD [...] ``` Node 系模式(注意与通用模式不同——COPY 的是源码不是产物): ```dockerfile FROM livebook:8418/images/runtime-node: COPY . . # .dockerignore 必须排除 node_modules ``` Node 系运行参数在 `docker run` 时经环境变量注入(`APP_ENTRY`/`BUILD_CMD`/`NPM_REGISTRY`/`PM2_APP_NAME`/`PM2_INSTANCES` 等,全表见 references),**不落镜像层**。已有 Dockerfile 若用了多段构建/装了 SDK/ARG 传令牌,按红线逐条指出并改写为单段。 → 验收:Dockerfile 单段、无编译步骤、无密钥;Node 系配套 `.dockerignore` 排除 node_modules。 ## 第 4 步:真机验证 完成定义 = 真机构建并起容器冒烟,不是"Dockerfile 语法没问题": 1. `docker build -t :test .` 成功 2. `docker run` 起容器,进程活着(Node 系首启会 install+build,启动慢是正常的,看日志等到 listening) 3. 有端口的验证端口可达 / 健康检查通过 环境不允许构建验证(宿主机没登记 `livebook:8418` 为 insecure-registry、无 docker)→ 显式声明"未验证",并把"待人工补"列进汇报。 ## 第 5 步:汇报 固定结构: - 模式:新建 / 评估修正 - 镜像源:内网 / 外网(探测结论,脚本输出佐证) - 探测结论:语言/栈 + 证据文件路径 - 选型:`runtime-:` + 一句话理由 - 【评估修正模式】审计结果:必须修 N 条 / 建议修 N 条 / 提示 N 条(附问题表),用户是否授权修正 - 写了什么:Dockerfile(新建/修改 diff)、`.dockerignore` - 被跳过/未做的:如多进程需求未确认、CI 构建步骤缺失、用户只让评估没让修 - 验证做到哪一步:build/run 结果,**没做就说没做** - 待人工补:daemon.json 登记、CI 构建产物步骤、secret 配置 ## 硬约束 - ① **编译不进 runtime 镜像**。为什么:镜像内无编译器/SDK(dotnet 无 SDK、java 无 JDK/Maven、python 无 musl 编译链),装了即失去"最小运行基座"意义;症状是构建期 `mvn/dotnet SDK not found`、pip 源码编译失败。唯一例外:Node 系启动时 install+build(自带 node-gyp 链,这是设计如此,不是漏洞)。 - ② **Node 系不 COPY node_modules**。为什么:entrypoint 启动时 `pnpm install`(有 lockfile 走 `--frozen-lockfile`),COPY 进去会覆盖冲突且镜像膨胀;原生模块在容器内编译才匹配运行环境。 - ③ **令牌不用 ARG 传、不落镜像层**。为什么:ARG 值留在 `docker history`;`NPM_TOKEN` 走 env 会出现在 `docker inspect` 输出。正确姿势:`NPM_TOKEN_FILE` 指 secret 文件(docker secret / k8s 挂载卷),令牌只写进容器内 `~/.npmrc`。 - ④ **生产不用 latest**。为什么:`latest` 每周一 02:30 随上游滚动重建,同一 tag 下面内容会变——上次能跑不代表下次能跑。锁版本标签或短 sha。 - ⑤ **C/C++ 仅 musl 动态链接适用 `runtime-cpp`**。为什么:Alpine 是 musl 系统,glibc 产物跑不起来,症状 `exec format error` 或 `not found`(二进制存在但加载器缺失)。glibc 产物请换基座或静态编译。 - ⑥ **Go/Rust 没有独立镜像**。静态原生二进制运行时不需要语言环境,`FROM runtime-base` 即可——不要去找 `runtime-go`。 - ⑦ **前端不走本系列**。React/Vue 等由 CI 构建静态文件,项目自己的 nginx 镜像托管。 - ⑧ **镜像源由前置探测决定,不猜不写死**。为什么:内网 `livebook:8418` 是 LAN 明文 HTTP,外网根本够不着;外网 `code.innev.cn` 在内网绕远。FROM 写死任何一路,换个网络环境就拉不动。每次任务先跑 `scripts/detect-registry.sh`;CI 等需要固定的场景用 `INNEV_IMAGE_PREFIX` 显式指定,并在汇报里说明。 ## 排障对照表 | 症状 | 原因 | 怎么办 | |---|---|---| | `exec format error` | glibc 二进制进 musl 镜像 / 架构不符 | 构建阶段改 musl 或静态编译;确认 buildx 架构 | | 拉镜像 `http: server gave HTTP response to HTTPS client` | 宿主未登记 `insecure-registries: ["livebook:8418"]` | 改 `/etc/docker/daemon.json` 并重启 docker(人工);或按前置探测改走外网前缀 | | 外网机器 FROM `livebook:8418` 超时/拒连 | 内网前缀只在内网可达 | FROM 换 `code.innev.cn/images/...`(重跑前置探测确认) | | 外网拉取 401 unauthorized | code.innev.cn 仓库要求认证 | `docker login code.innev.cn`(人工,用 Gitea 账号) | | pip 安装编译失败 | runtime-python 无编译链 | 构建阶段(CI)编译好 wheel/venv 再 COPY | | .NET 程序在 runtime-dotnet 起不来 | Framework 4.x 程序走了现代 .NET 镜像 | 换 `runtime-wine-dotnet`,`xvfb-run -a wine` 启动 | | 令牌出现在 `docker inspect` | NPM_TOKEN 走了 env | 改 NPM_TOKEN_FILE secret 挂载 | | 上周能跑这周不能 | 用了 latest,周一滚动重建 | 锁版本标签/短 sha | | Node 容器启动很慢 | 正常:启动时 install+build | 看日志等到 listening;别当挂起杀掉 | ## 红线(任何情况下不违反) - 不凭猜测选型——第 0 步探测不到证据就问,不硬塞近似镜像 - 不给项目 Dockerfile 加编译段/SDK(Node 系启动时构建除外) - 不在 Dockerfile/镜像层里写任何令牌密钥;用户贴过来的令牌不落进任何文件,并提醒轮换 - 不静默截断——跳过的需求(多进程/私有源/健康检查)必须出现在汇报里 - 改已有 Dockerfile 前先给结论和 diff,确认后再写