# Dockerfile详解

### FROM

1. 指定基础镜像，必须是第一条指令
2. 如果没有基础镜像，则以 `FROM scratch` 开头
3. 这条指令写入的东西为镜像首层

**写法**： `FROM <images>:<tag>`

### USER

1. 设置容器启动用户，可以是用户名或UID
2. 如果容器以daemon用户运行，那么`RUN`, `CMD`, `ENTRYPOINT` 都会以这个用户运行

**语法**：

```
USER daemo
USER UID
```

### ENV 指令

1. 设置容器环境变量

**语法**：

```
ENV <key> <value>
ENV <key>=<value> ... // 可以设置多个
```

### WORKDIR

1. 设置工作目录，对 `RUN`, `CMD`, `ENTRYPOINT`, `COPY`, `ADD` 生效。
2. 如果不存在则会创建， **也可以设置多次**
3. 可以指定绝对路径和相对路径，这里的路径是指容器中的路径
4. `WORKDIR` 会解析环境变量
5. 可以理解为cd 命令

**语法**：

```
WORKDIR /path/to/workdir
WORKDIR path
WORKDIR /$PATH
```

### RUN

1. RUN命令在容器的可写入层执行命令，并commit容器为新的镜像。
2. 上一步RUN命令生成的镜像被接下来RUN使用，每次RUN命令都会重新生成一个新的镜像
3. 可以通过链式输入命令以减少创建镜像层的数量
4. 镜像层过多，会导致镜像过大
5. 可以通过`docker history 镜像名称` 查看镜像层
6. CMD 命令通常在文件最末尾处

**写法**： `RUN <command>` 或者 `RUN ["executable", "param1", "param2"]`

第二种方式类似函数调用，在linux下，自动使用脚本解析器： `/bin/sh -c` ，windows下则是 `cmd /S /C`

为了减少镜像层数，可以使用链式命令 `RUN command1&& command2` 并且，可以使用 `\反斜线` 换行。

### CMD

1. CMD 命令用于指定启动容器时的命令
2. 如果不指定CMD命令，则将使用基础镜像提供的默认命令
3. 因为是启动命令，所以在创建容器时不会执行，只有启动容器时才会执行
4. 也可以使用exec形式指定要执行的命令
5. 可以使用`docker run -ti centos:7 echo "hello world"` 的方式，替换默认命令
6. 写法：`CMD ["echo", "hello"]`
7. 只可以写一条，如果写多条，只有最后一条有效

### ENTRYPOINT

功能是启动时默认命令，与CMD类似

**语法**：

```
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
```

**相同点**：

1. 只可以写一条，如果有多条，只有最后一条有效
2. 容器启动时相同

**不同点**：

1. ENTRYPOINT 指令不会被运行时command覆盖

### COPY

1. COPY 指令从build上下文复制文件或者文件夹到容器文件系统
2. 例如： `COPY xxx.jar /data/jar/`

### ADD

1. ADD指令和COPY指令类似，可以将文件复制到容器中
2. 也可以从Internet下载文件并复制到容器中
3. 并且，ADD命令可以自动解压压缩文件
4. 通常使用COPY指令，除非明确需要ADD指令

```
ADD test relativeDir/
ADD test /relativeDir
ADD http://example.com/foobar /
```

### VOLUME

1. 可实现挂载
2. 容器使用的是AUFS，这种文件系统不能持久化数据，当容器关闭后，所有的更改都会丢失
3. 数据资源推荐挂载

**语法**：

```
VOLUME ["/data"]
VOLUME /var/log
VOLUME /var/log /var/db
```

### LABEL

LABEL 指令为镜像指定标签， 一个镜像可以拥有多个标签

**语法**：

```
LABEL <key>=<value> <key>=<value>

或

LABEL <key>=<value>
LABEL <key>=<value>
```

> 可以使用 反斜杠换行，或者多个LABEL 标签

### MAINTAINER

指定作者：

```
MAINTAINER <name>
```

### ARG

1. 设置变量命令
2. ARG命令定义一个变量，在docker build 使用Dockerfile构建镜像时，使用 `--build-arg <varname>=<value>` 来指定变量值
3. 可以给变量设置默认值
4. 如果用户在build镜像时指定了一个参数没有定义在Dockerfile中，那么将有一个Warning`[Warning] One or more build-args [foo] were not consumed.`

**语法**：

```
FROM busybox
ARG user1=someuser
ARG buildno # 给默认值
```

### HEALTHCHECK

**语法**：

```
# 在容器内部运行一个命令进行健康检查
HEALTHCHECK [OPTIONS] CMD command

# 在基础镜像中取消健康检查命令
HEALTHCHECK NONE
```

OPTIONS 支持如下三个选项：

```
--interval=DURATION 两次检查默认的时间间隔为30秒
--timeout=DURATION 健康检查命令运行超时时长，默认30秒
--retries=N 当连续失败指定次数后，则容器被认为是不健康的，状态为unhealthy，默认次数是3
```

CMD命令的返回值，决定了本次健康检查是否成功：

```
0: success - 表示容器是健康的
1: unhealthy - 表示容器已经不能工作了
2: reserved - 保留值
```

比如：

```
HEALTHCHECK --interval=5m --timeout=3s \CMD curl -f http://localhost/ || exit 1
```

HEALTHCHECK 指令只可以出现一次，如果出现多次，最后一个生效。

### ONBUILD

1. 只对当前镜像的子镜像生效

比如当前镜像为A，在Dockerfile种添加： `ONBUILD RUN ls -al` 这个 `ls -al` 命令不会在A镜像构建或启动的时候执行

此时有一个镜像B是基于A镜像构建的，那么这个ls -al 命令会在B镜像构建的时候被执行。

**语法**：

```
ONBUILD [INSTRUCTION]
```

### STOPSIGNAL 指令

**语法**：

```
STOPSIGNAL signalSTOPSIGNAL
```

命令是的作用是当容器推出时给系统发送什么样的指令

### 使用Dockerfile构建image

cd 到 Dockerfile 文件所在路径执行 `docker build -t 镜像名称:版本 .` 即可构建镜像

#### 镜像构建缓存

![file](/files/2wIxK0kUOylKv1fzbHNG)

如果新的构建版本中，某些指令与之前的版本一致，则会使用缓存。

**造成的问题**：

1. 如果是`yum update -y` 命令，则不会执行，导致yum仓库源不是最新的

**解决办法**：

1. 保证命令前后不一致，可以使用链式命令
2. 使用 –no-cache=true`docker build -t centos . --no-cache=true`

#### 上传镜像至 DockerHub

1. docker login
2. docker push \[OPTIONS] NAME\[:TAG]

### 构建多CPU架构image

**创建以下dockerfile：**

```dockerfile
FROM alpine:latest

LABEL maintainer="yangsx.yangsx95@qq.com"

RUN apk update && \
        apk add git curl
```

**安装buildx：**

在mac与win的docker-desktop自带buildx，而linux需要安装：<https://github.com/docker/buildx>

**创建并使用一个新的builder（默认builder可构建的cpu架构类型少）：**

```shell
$ docker buildx create --use --name mybuilder
mybuilder
```

**查看所有的builder列表：**

```shell
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker
  default default         running linux/amd64, linux/arm64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
```

**查看当前使用的builder：**

```shell
$ docker buildx inspect --bootstrap
docker buildx inspect --bootstrap
Name:   mybuilder
Driver: docker-container

Nodes:
Name:      mybuilder0
Endpoint:  unix:///var/run/docker.sock
Status:    running
Platforms: linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
```

**使用buildx构建多cpu架构的镜像：**

```shell
# --platform 指定支持的cpu架构
docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 -t <image_name> .

=> 

docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 -t yangsx95/gitclient . 
```

\*\*成果：\*\*<https://hub.docker.com/repository/docker/yangsx95/gitclient>

### 多阶段构建

可以使用Dockerfile拉取环境镜像，分阶段构建最终镜像。比如一个java应用，依次经过三个阶段：

1. `git clone` 拉取代码
2. `mvn clean package` 打包
3. 放入tomcat容器

```dockerfile
FROM yangsx95/gitclient:latest as cloner
MAINTAINER "yangsx.yangsx95@qq.com"
WORKDIR /opt
# 拉取代码
RUN git clone https://github.com/lizhenliang/tomcat-java-demo.git

FROM maven:3.8-openjdk-8 as builder
MAINTAINER "yangsx.yangsx95@qq.com"
WORKDIR /opt
# 将cloner阶段的opt/tomcat-java-demo这个文件拷贝到当前阶段的/opt/路径下
COPY --from=cloner /opt/tomcat-java-demo/ /opt/
# 添加阿里云maven镜像
RUN echo '<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"><localRepository>/usr/share/maven/ref/repository</localRepository><mirrors><mirror><id>aliyunmaven</id><mirrorOf>*</mirrorOf><name>阿里云公共仓库</name><url>https://maven.aliyun.com/repository/public</url></mirror></mirrors></settings>' > /opt/settings.xml
# 执行构建
RUN mvn clean package --settings=/opt/settings.xml -Dmaven.test.skip=true

FROM tomcat:8-jdk8 as runner
MAINTAINER "yangsx.yangsx95@qq.com"
WORKDIR /usr/local/tomcat/webapps/
COPY --from=cloner /opt/  /opt/test
COPY --from=builder /opt/target/*.war /usr/local/tomcat/webapps/ROOT.war
```

> \--from可以指定名称，也可以指定索引，比如引用第一个阶段的结果，使用`--from=0`

将上述内容保存到`Dockerfile`文件中，使用下面的命令构建镜像：

```
docker build -t yangsx95/hello-girl .
```

测试：

```
docker run -p 8080:8080 --rm  yangsx95/hello-girl
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/devops/container/docker/dockerfile-xiang-jie.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
