镜像构建时使用 ARG 导致镜像缓存失效

Posted by icebergu on 11-06,2020

工作中发现项目在镜像构建时,即使没有修改 go.mod 也依然会执行 go mod download 来拉取依赖,而不是使用镜像缓存,导致每次构建时间都很长
经过排查发现是使用 ARG 导致的镜像缓存失效

构建镜像时可以使用 ARG 指令来设置构建环境的环境变量

  1. 首先在 Dockerfile 中 ARG 指令定义参数名称,设置默认值
    ARG <name>[=<default value>]
  2. docker build 时使用 --build-arg <name>=<value> 来传递参数

ARG 虽然和 ENV 指令一样都是设置环境变量,不过 ARG 设置的是构建环境时的环境变量,在容器运行时不会存在这些环境变量

ARG 指令可以在 FROM 指令前指定,也可以在 FROM 指令后使用

ARG 指令在 FROM 指令前

ARGFROM 指令前指定,那么参数只能在 FROM 指令中使用
并且 ARG 的参数可以用在每个 FROM 指令中

ARG TAG=1.13.0
FROM golang:${TAG}
RUN echo tag:${TAG}

FROM golang:${TAG}
RUN echo tag:${TAG}

镜像构建时,FROM 指令会使用 TAG 的值,但是 RUN 指令并不会打印出 TAG 的值

$ docker build --build-arg TAG=latest --no-cache .
Sending build context to Docker daemon  2.048kB
Step 1/5 : ARG TAG=1.14.0
Step 2/5 : FROM golang:${TAG}
 ---> c4d6fdf2260a
Step 3/5 : RUN echo tag:${TAG}
 ---> Running in 9ee89387b298
tag:
Removing intermediate container 9ee89387b298
 ---> 8cd6b2627cad
Step 4/5 : FROM golang:${TAG}
 ---> c4d6fdf2260a
Step 5/5 : RUN echo tag:${TAG}
 ---> Running in d3f2673a7f71
tag:
Removing intermediate container d3f2673a7f71
 ---> e1a926150795
Successfully built e1a926150795

ARG 指令在 FROM 指令后

ARGFROM 指令后的话,只会对当前的构建阶段有效
并且如果 ARG 参数的值修改,那么后续指令都可能不会使用镜像缓存

ARG 可能会使缓存失效这个问题就导致在使用的时候需要特别注意,如何过早的使用 ARG 指令可能会使后续一些依赖拉取之类的 RUN 指令强制执行,而不是使用之前的镜像缓存

ARG 导致镜像缓存失效

构建 golang 项目时,make build 时会使用 BUILD_VERSION 这个环境变量,所以需要在 docker build 时将 BUILD_VERSION 传递进去

以下 Dockerfile 为精简版示例,不可用于生产开发

FROM golang:1.15.0
ARG BUILD_VERSION

COPY go.mod .
COPY go.sum .
RUN go mod download

COPY . .
RUN make build

问题

对于拉取依赖,只有在 go.mod,go.sum 修改时再去执行即可,其他情况应该使用镜像缓存
但是如果 BUILD_VERSION 发生修改,那么后续指令都不会使用镜像缓存,需要再次执行 RUN go mod downlond

建议

ARG 指令应该在接近使用的地方去执行,防止其他指令的镜像缓存失效

COPY . .

ARG BUILD_VERSION
RUN make build