极简工具箱

免费在线工具 · 本地计算 · 无需上传

Dockerfile 怎么写:分层缓存、多阶段构建与镜像瘦身

同一个应用,有人打出来的镜像 1.2GB,有人只有 80MB。差别不在应用,在于 Dockerfile 怎么写、基础镜像怎么选、层怎么排。

广告位(百度联盟)

一、指令的分工

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
指令作用注意
FROM基础镜像alpine 最小,slim 折中
WORKDIR工作目录后续指令的相对基准
COPY复制文件比 ADD 更可预测(ADD 会自动解压)
RUN构建时执行每条产生一层
CMD默认启动命令可被 docker run 的参数覆盖
ENTRYPOINT固定入口参数追加在其后

CMD 与 ENTRYPOINT 的经典组合:ENTRYPOINT ["node"] + CMD ["server.js"],运行时 docker run img other.js 会执行 node other.js。

二、缓存是按层命中的

Docker 逐层比对,某一层发生变化,它之后的所有层缓存全部失效。所以顺序很关键:

# 好:依赖清单先拷,代码后拷
COPY package*.json ./
RUN npm ci
COPY . .

# 差:先拷全部代码,改一行就重装依赖
COPY . .
RUN npm ci

package.json 不常变,npm ci 这一层就能长期复用;源码改动只让最后的 COPY . . 失效。这是构建加速最有效的一招,Python(requirements.txt)、Go(go.mod)同理。

三、多阶段构建:把编译工具留在构建阶段

# 阶段一:构建
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

# 阶段二:运行
FROM alpine:3.20
RUN adduser -D -u 10001 app
USER app
COPY --from=build /out/app /usr/local/bin/app
ENTRYPOINT ["app"]

最终镜像只含二进制和最小运行环境,编译器、源码、缓存全部丢弃。Node 项目同理:构建阶段装 devDependencies 打包,运行阶段只 npm ci --omit=dev。

四、瘦身的六个动作

  1. 选 alpine / slim / distroless,别用完整版 ubuntu。
  2. 多阶段构建,运行镜像只留产物。
  3. RUN 里合并命令并清理缓存:
   RUN apt-get update && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*
  1. .dockerignore 排除 node_modules、.git、日志、测试数据。
  2. 避免在镜像里装编译工具(gcc、make)——运行时不需要。
  3. 不要 COPY . 到最终镜像,只拷必要文件。

五、.dockerignore 一定要写

node_modules
.git
*.log
dist
.env

否则 COPY . . 会把宿主机的 node_modules(可能几百 MB、还带平台相关的二进制)一起塞进镜像,覆盖容器里正确安装的依赖,出现「本地能跑、容器报错」的怪问题。

六、安全实践

七、构建与验证

docker build -t myapp:1.0 .
docker run --rm -it myapp:1.0 sh        # 进去看目录结构
docker history myapp:1.0                # 看每层大小,找出膨胀点
docker images myapp                     # 对比体积

docker history 是找「哪一层把镜像撑大了」的最快方式。

小结

先拷依赖清单再拷代码以命中缓存;多阶段构建分离构建与运行;.dockerignore 排除 node_modules 和 .git;用非 root 用户;固定基础镜像版本。

本文相关工具(本地计算,无需上传)

更多使用教程