
镜像体积大多半是 Dockerfile 没做多阶段构建。原文第 5 章用 golang:1.22 AS builder 编译、alpine:3.20 运行就是在解决这个问题。TaoToken 的接入也直接打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建 API Key把 Base URL 填成 https://taotoken.net/apiCodex 就能按 builder/alpine 分段重排 Dockerfile 各层。跑通一次“给运行层瘦身”的请求顺便在控制台验证 TaoToken 的用量记录。真正动手时我见过很多人的 Dockerfile 连多阶段都没用一个 FROM 从头吃到尾镜像能不胖吗。1. 镜像虚胖的根源golang:1.22 被当成运行层1.1 把编译工具带进运行层的典型写法新手写 Dockerfile最省事的写法就是只放一个 FROM。比如一个 Go 项目很多人的第一版长这样FROM golang:1.22 WORKDIR /app COPY . . RUN go build -o myapp . CMD [./myapp]这段配置在本地能跑但 golang:1.22 是带完整 Go 工具链的镜像体积通常在 800MB 以上。编译完 myapp 之后编译器、标准库源码、module 缓存全留在镜像里运行阶段根本用不到。本地调试图省事还能忍一旦镜像要推送到仓库、要过 CI、要给整个团队拉取这 800MB 的成本就会被放大很多倍。原文第 5 章指出的正是这个问题构建依赖只服务于“构建”这个动作一旦编译完成它们就该被丢掉。Go 的二进制本身就是静态编译的最佳候选所以 Go 项目是最适合先做多阶段改造的一类。等这一版跑通你再把同样的思路套到 Node 前端镜像上逻辑是一样的装依赖的阶段用完整镜像产出静态文件后交给 nginx 镜像去跑。1.2 第 5 章的多阶段构建FROM 拆成两段原文给的方向是用两个 FROM 把流程拆开第一段负责编译第二段负责运行# 构建阶段 FROM golang:1.22 AS builder WORKDIR /app COPY . . RUN go build -o myapp . # 运行阶段 FROM alpine:3.20 WORKDIR /app COPY --frombuilder /app/myapp . CMD [./myapp]第一段 builder 保留完整编译环境第二段换成 alpine:3.20。alpine 本身只有几 MB拷进去的 Go 二进制如果是静态编译最终镜像能压在 20MB 左右。对比单阶段的 800MB这不是“优化”是量级上的差距。但知道归知道真到自己动手时问题不少COPY --from 的路径写错、CGO 默认打开导致二进制依赖 glibc、alpine 里缺 ca-certificates 导致 HTTPS 请求失败。这些细节原文第 5 章没有逐一展开正好适合丢给 Codex 来处理。2. Codex 走 TaoToken改 ~/.codex/config.toml 指向新通道2.1 创建 Key模型广场里挑模型用 Codex 改 Dockerfile前提是 Codex 后面有一个能正常应答的模型通道。先打开 TaoToken 注册并登录。登录后建议先看一眼模型广场确认当前可用的模型 ID再到 API Keys 页面创建一把 Key。Key 创建后只完整显示一次记得立刻复制。这把 Key 会绑定到你的账号后续所有通过 Codex 发起的请求都记在它的用量里。如果后面对接的机器不止一台可以按机器分别创建 Key出问题的时候方便定位是哪一个环境在消耗。2.2 codex config.tomlmodel_provider 指向 API 通道Codex 的自定义模型供应商配置写在 ~/.codex/config.toml 里。文件不存在就新建填入以下内容model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEYYOUR_MODEL_ID 以模型广场当时列表为准base_url 固定填 https://taotoken.net/api末尾不要加 /v1。然后执行export TAOTOKEN_API_KEYYOUR_API_KEYYOUR_API_KEY 是刚才创建的那把 Key。Codex 读取 config.toml 里的 env_key 字段名从环境变量里取出真正的 Key附加到每次请求上。配置完成后Codex 的代码生成和对话请求都走 TaoToken 通道不再依赖机器上原有的模型凭据。这里的 model_provider 字段决定了 Codex 用哪一组连接参数。只要 config.toml 语法正确Codex 会在启动时读取 [model_providers.taotoken] 这一段把 base_url 替换成请求地址。如果你同时在用多个供应商改 model_provider 的值就能切换不用动环境变量。配置里没有出现任何 Key 明文Key 只存在于环境变量中这样即使 config.toml 被提交到仓库也不会泄露密钥。3. 让 Codex 重排 Dockerfilebuilder 留编译alpine 只跑二进制3.1 给 Codex 的提示词把层拆分要求说清楚进入项目目录启动 codex然后输入这个 Go 项目的 Dockerfile 是单阶段的镜像体积一直在 800MB 以上。请按多阶段构建重写builder 阶段用 golang:1.22 负责编译运行阶段用 alpine:3.20 只保留二进制。额外说明 CGO_ENABLED 为什么需要关闭并给出 .dockerignore 的建议。提示词里最好带上两个信息当前的镜像体积让 Codex 知道问题有多严重项目的语言和构建方式比如是 go build 还是带 build tags。Codex 会根据这些信息决定 COPY 哪些文件、RUN 里放什么命令。这里要明确边界Codex 负责生成和解释 Dockerfiledocker build 由你在本地终端执行。不要让它直接操作 Docker daemon也不要把它接进生产环境。这样既保证建议可落地又能避免 AI 工具越权操作你本机以外的资源。3.2 Codex 返回的结果依赖留在 builder运行层只剩二进制Codex 走通一次请求后返回的 Dockerfile 大致长这样# 构建阶段保留完整 Go 工具链 FROM golang:1.22 AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 GOOSlinux go build -o myapp . # 运行阶段只保留可执行文件 FROM alpine:3.20 RUN apk add --no-cache ca-certificates WORKDIR /app COPY --frombuilder /app/myapp . CMD [./myapp].dockerignore 则建议把 .git、测试目录、本地日志排除出构建上下文.git/ *.log Dockerfile* test/这里有两个关键动作。第一CGO_ENABLED0 生成静态链接二进制运行阶段不需要额外的动态库alpine 里少了 glibc 也照样启动第二COPY go.mod go.sum 放在 COPY 源码之前源码改动时不会触发 go mod download 重新执行这是原文第 5 章“利用构建缓存”的落法。类似的技巧还有把 RUN 里要安装的多个包按字母排序便于审查也便于命中缓存。如果你还想继续压可以让 Codex 把 builder 再拆成 base 和 build 两段前者统一装依赖、后者做真正的编译对应原文“创建可重用的阶段”。一个 base 能同时服务多个服务镜像比每次从头拉依赖要省时间。如果项目里有多个服务共用同一套 Go 依赖这个 base 阶段的价值会非常明显。4. 验证调用docker images 对比瘦身前后体积4.1 docker build 与 docker images 本地验证拿到 Codex 的修改后替换 Dockerfile在项目目录执行docker build --no-cache -t myapp:multi-stage . docker images myapp如果之前构建过单阶段版本docker images 会同时列出两个镜像。单阶段镜像通常在 900MB 上下多阶段后一般只有 20MB 左右。这就是运行层瘦身最直观的证明。再运行一次容器确认没有跑挂docker run --rm myapp:multi-stage这一步由你在本地执行。如果容器启动报错把 stdout 和错误信息贴回 Codex 对话让它继续调整运行阶段的指令。常见的报错集中在动态链接库缺失和 ca-certificates 没装Codex 看到报错一般会直接补上对应的 apk add。想看更细的话用 docker history 查看镜像层数多阶段镜像的层数明显少于单阶段因为编译器、临时文件这些中间层根本没进最终镜像。层数少还有一个附带好处推送和拉取的时间都变短了。4.2 回到 TaoToken 控制台确认这次调用构建验证完回到 TaoToken 控制台的用量页面。Codex 每轮对话都会向 base_url 发起请求这些请求会累计到你刚才那把 Key 下面。如果用量页面能看到对应的请求记录说明从 Codex 到 TaoToken 的整条链路已经打通。这一步也是判断配置是否真的生效的关键有些人配置完后 Codex 回退到了默认模型表面看也能回答实际上根本没走自定义通道。去控制台对一下用量比在终端里反复试更直接。用量记录里能看到请求时间、模型 ID 和消耗的 token 数和本地操作时间对照一下就清楚了。5. 排障Codex 和 TaoToken 之间常见的三个断点5.1 401TAOTOKEN_API_KEY 没被 Codex 读到如果 Codex 返回 401先检查环境变量是否真的设置成功echo $TAOTOKEN_API_KEY输出为空说明 export 没有生效或者终端重开后丢失了。把 export TAOTOKEN_API_KEYYOUR_API_KEY 写进 ~/.zshrc 或 ~/.bashrc再 source 一下避免每次重开终端都要重新设置。还需要确认 config.toml 里的 env_key 字段名与 export 的变量名完全一致Codex 严格按这个名字去取环境变量差一个字母都会导致 401。5.2 base_url 末尾多了 /v1config.toml 里的 base_url 只填 https://taotoken.net/api。很多模型平台的地址习惯带 /v1照搬会让 Codex 拼出错误的路径表现为连不上或者请求失败。确认 [model_providers.taotoken] 下的 base_url 没有多余后缀改完保存后重启 codex。5.3 模型 ID 不在模型广场列表里config.toml 里的 model 字段填的是模型 ID不是展示名。模型会上线下线ID 也可能调整所以别拿文章里的旧 ID 直接填。打开模型广场以当时列表里的 ID 为准。填错的表现通常是请求发出后返回模型不存在的报错这时候回模型广场复制最新的 ID 换上即可。遇到连不上时还有一个容易忽略的点Codex 进程是否在 config.toml 修改之后才启动。config.toml 只在启动时读一次改完不重启Codex 用的还是旧配置。把终端里的 codex 退出重进再发一条消息试一次。6. 下一步去控制台对一下这次 Codex 调用的用量6.1 先用模型对话印证拆层建议刚才那轮 Codex 请求已经在用量里出现说明 Key 和 base_url 都正确。如果你想在浏览器里用同一个模型再验证一次 Dockerfile 拆层建议打开 TaoToken 模型对话把 alpine 运行层的 Dockerfile 贴进去对比模型的回答和 Codex 是否一致。两边指向同一个模型时拆层思路不会差太远也能顺带确认同一把 Key 在网页端和 Codex 端都能正常消费。6.2 用量够不够看 Coding Plan多阶段构建改完只是第一步后面还有 Compose、CI/CD 一系列镜像要处理Codex 的调用量会涨得很快。到 Coding Plan 页面看看当前套餐够不够用。给其他机器配新 Key去 API Keys 控制台 创建如果接下来想在 Claude Code 里做同样的镜像瘦身参考 Claude Code 接入文档。镜像体积降下来之后构建缓存和层复用才是日常效率的大头这两块可以继续让 Codex 按项目实际情况给方案。前提依然是 Codex 后面有一条稳定的模型通道而判断通道是否稳定的方法就是随时回到控制台看用量。