> 工作中发现项目在镜像构建时,即使没有修改 go.mod 也依然会执行 go mod download 来拉取依赖,而不是使用镜像缓存,导致每次构建时间都很长
> [经过排查发现是使用 ARG 导致的镜像缓存失效](http://icebergu.com/archives/docker-build-arg#arg-%E5%AF%BC%E8%87%B4%E9%95%9C%E5%83%8F%E7%BC%93%E5%AD%98%E5%A4%B1%E6%95%88)
构建镜像时可以使用 `ARG` 指令来设置构建环境的环境变量
1. 首先在 Dockerfile 中 `ARG` 指令定义参数名称,设置默认值
`ARG <name>[=<default value>]`
2. `docker build` 时使用 `--build-arg <name>=<value>` 来传递参数
`ARG` 虽然和 `ENV` 指令一样都是设置环境变量,不过 `ARG` 设置的是构建环境时的环境变量,在容器运行时不会存在这些环境变量
`ARG` 指令可以在 `FROM` 指令前指定,也可以在 `FROM` 指令后使用
### `ARG` 指令在 FROM 指令前
**`ARG` 在 `FROM` 指令前指定,那么参数只能在 `FROM` 指令中使用**
**并且 `ARG` 的参数可以用在每个 `FROM` 指令中**
```yaml
ARG TAG=1.13.0
FROM golang:${TAG}
RUN echo tag:${TAG}
FROM golang:${TAG}
RUN echo tag:${TAG}
```
镜像构建时,`FROM` 指令会使用 TAG 的值,但是 `RUN` 指令并不会打印出 TAG 的值
```bash
$ 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` 指令后
`ARG` 在 `FROM` 指令后的话,只会对当前的构建阶段有效
**++并且如果 `ARG` 参数的值修改,那么后续指令都可能不会使用镜像缓存++**
**`ARG` 可能会使缓存失效这个问题就导致在使用的时候需要特别注意,如何过早的使用 ARG 指令可能会使后续一些依赖拉取之类的 `RUN` 指令强制执行,而不是使用之前的镜像缓存**
### `ARG` 导致镜像缓存失效
构建 golang 项目时,`make build` 时会使用 `BUILD_VERSION` 这个环境变量,所以需要在 `docker build` 时将 `BUILD_VERSION` 传递进去
> 以下 Dockerfile 为精简版示例,不可用于生产开发
```yaml
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
```
镜像构建时使用 ARG 导致镜像缓存失效