Dockerfile 生成
按项目类型生成多阶段 Dockerfile,依赖缓存、产物拷贝、非 root 用户和 .dockerignore 一并给出。
需要注意
- .env 已经在 .dockerignore 里排除。密钥、令牌这类东西不要写进 ENV 或 ARG——它们会留在镜像层里,docker history 能看到;运行时用 --env-file 或编排的 secrets 传。
生成规则
Node 项目拆成三段:deps 只装依赖,build 带上源码跑构建,runner 重新装一份生产依赖并只拷贝构建产物。三段共用同一个基础镜像 tag,缓存命中率最高。
用 pnpm 或 yarn 时会先 corepack enable。npm ci 和 pnpm install --frozen-lockfile 都要求存在锁文件,提交仓库时不要漏掉 package-lock.json / pnpm-lock.yaml,否则构建会直接失败。
Go 用 CGO_ENABLED=0 编成静态二进制,运行时阶段可以只放一个 distroless 镜像。distroless 里没有 shell,进容器排查要另想办法,这是换小体积的代价。
静态站的产物交给 nginx,需要你在同目录放一份 nginx.conf,里面用 try_files $uri $uri/ /index.html 处理前端路由。想直接生成这份配置,用Nginx 配置生成。
常见问题
为什么要先 COPY package.json 再装依赖?
Docker 的每条指令都是一层缓存,只要输入没变就直接复用。把依赖清单单独拷进去、先装依赖再拷源码,改业务代码时依赖层不会失效;反过来写的话,改一行代码就要重装一遍所有包。
多阶段构建省在哪?
构建阶段有编译器、devDependencies 和源码,运行时阶段只拷走产物和运行时依赖。最终镜像里没有这些构建工具,体积通常能小一半以上,攻击面也小。
alpine 和 slim 怎么选?
alpine 更小,但用的是 musl libc,依赖 glibc 的预编译二进制(部分 Node 原生模块、老版本 Python 包)可能装不上,需要额外装编译工具。遇到莫名其妙的构建失败,先换 slim 试。
非 root 用户有什么实际影响?
容器里以 root 运行时,一旦进程被拿下,攻击者在容器内就是 root,配合内核漏洞更容易逃逸。改成普通用户后,写文件、监听低位端口都可能失败,需要提前把目录属主和端口调好。
为什么 .dockerignore 里要排除 .env?
构建上下文里所有文件都会先发给 Docker 守护进程,.env 会被一起传过去;如果 Dockerfile 里写了 COPY . .,它还会被复制进镜像层。镜像层一旦推送到仓库,里面的密钥就等于公开了,删掉文件也去不掉历史层。
生成的镜像怎么跑起来?
先 docker build -t 名字 .,再 docker run --rm -p 宿主端口:容器端口 --env-file .env 名字。环境变量在运行期注入,不要写进 Dockerfile 的 ENV——ENV 会固化在镜像里。