Dockerfile文件的使用

在上一篇文章 Docker的必要性及基础使用,我们知道 Dockerfile 是一个文本文件。这个文件里面包含了一系列的指令,每一条指令构建一层,每一条指令的内容及就是描述该层是如何构建的。

Dockerfile文件格式

我们可以上篇文章 Docker的必要性及基础使用Demo 的Dockerfile文件:

1
2
3
4
5
6
FROM node:12.17.0
COPY . /shuliqi
WORKDIR /shuliqi
RUN ["npm", "install"]
EXPOSE 3000/tcp
CMD node app.js

因此我们知道Dockerfile 文件的格式如下:

1
2
# comment
INSTRUCTION arguments
1
2
# 注释
指令 参数

Dockerfile文件中的指令是不区分大小写的,但是为了更容易区分,约定使用 大写形式

Docker会依次执行Dockerfile文件中的指令,文件中第一条指令必须是FROM

#开头的行,Docke会认为是注释,但是 # 出现在指令参数中,则不是注释。如:

1
2
# Comment
RUN echo 'how old are you # my name is shuliqi'

Dockerfile 的组成部分

Dockerfile 文件主要由 四部分组成,分别是:

部分 指令
基础镜像信息 FROM
维护者信息 MAINTAINER
镜像操作指令 RUN,COPY,ADD,EXPOSE、WORKDIR、ONBUILD、USER、VOLUME等
容器启动时执行指令 CMD、ENTRYPOINT

Dockerfile中的指令

接下来我们讲指令的同时结合例子来讲,首先我们先创建一个可以使用的例子。关于怎么创建可以参考上一篇 博文Docker的必要性及基础使用。我创建了项目 **dockerfile-example**。当然项目顺便你怎么弄,直接减一个Dockerfile的文件也算是个。

FROM

FROM指令为后面的指令提供镜像基础。FROM指令必须是Dockerfile文件的首条命令,启动构建流程后,Docker将会基于该镜像构建新镜像,FROM后的命令也是基于这个基础镜像。

FROM的语法格式:

1
FROM <image>

1
FROM <image>:<tag>

1
FROM <image>:<diaest>

通过 FROM指定的镜像,可以是任何有效的基础镜像,FROM有以下的限制:

  • FROM必须是 Dockerfile文件的第一条非注释命令
  • 在一个 Dockerfile文件中创建多个镜像时,FROM是可以多次出现。只需要在每个新命令FROM之前,记录提交上次的镜像ID
  • tagdigest是可选的,如果不使用这两个值的时候,会使用latest版本的基础镜像

举个🌰:

在我们当前的项目中, 我们使用node的基础镜像。就可以这么写

1
FROM node:8.4

Docker Hub有很多高质量的官方镜像。是可以直接拿来使用的。

除了选择现有的镜像为基础镜像外。Dcoker 还有一个比较特殊的镜像叫scratch。这个镜像是虚拟的概念。并不实际存在、表示一个空白的镜像。我们也可以使用:

1
FROM scratch

如果使用了 scratch为基础镜像的话,就说明不以任何的镜像为基础。接下来编写的指令将作为镜像的第一层存在。

RUN

RUN用来在定制镜像(image)时执行命令行命令的,有两种命令执行方式:

shell 执行

这种方式会在shell中执行命令,就像直接在命令行输入命令一样。

shell格式:

1
2
run <命令行命令>
# <命令行命令> 等同于在终端操作的 shell 的命令

整理了一份 命令行命令。可翻阅

举个🌰:

我们希望在定制镜像(image)是新建一个名字为 shuliqi.js 的文件。我们就可以这么写:

1
RUN touch shuliqi.js

然后我们定制镜像(image)文件docker build -t dockerfile-example .然后以该镜像(image)启动一个容器docker run -t dockerfile-example 最后我们进入到这个容器看看:

1
2
3
4
5
6
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0c3ac932b755 dockerfile-example "/bin/bash" 59 seconds ago Up 58 seconds compassionate_nobel
$ docker exec -t -i 0c3ac932b755 bash
root@0c3ac932b755:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin shuliq.js srv sys tmp usr var

进入到容器之后, 我们使用ls 命令行命令,可以看出来,有文件名字为:shuliqi.js

exec执行

exec格式:

1
2
3
RUN ["可执行的额文件",“参数1”, "参数2"]
## 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline

RUN可以执行任何命令,然后再当前镜像上创建一个新层并且提交,提交后的结果镜像将会在 Dockerfile文件的下一步。

通过RUN执行多条命令时,可以通过\换行执行:

1
2
RUN touch shuliqi.js\
touch shuliqi2.js

也可以在同一行通过分好分隔命令:

1
RUN touch shuliqi.js; RUN touch shuliqi.js; \

注意:RUN指令创建的中间镜像会被缓存,并且在下次构建中使用。如果不想使用这些缓存镜像,可以在创建时指定--no-cache参数,如:

1
ocker build --no-cache .

例如:

这样构建镜像的步骤就没有使用缓存,使得每一层的镜像ID都与之前的不同。

CMD

CMD用于在指定容器启动时所有执行的命令。CMD有以下三种格式:

1
2
3
CMD <commond> // shell 格式
CMD ["executable", "param1", "param2"] // exec格式,推荐格式
CMD ["param1", "param2"] // 为ENTRYPOINT指令提供参数

RUM命令不同的是:

RUM指令在构建镜像时要执行命令。CMD则是用于在指定的容器启动时所要执行的命令。

CMD在 Dockerfile文件中仅可指定一次,指定多次时,会覆盖前面的指令。

需要注意:docker run 命令会覆盖CMD命令。 如果 docker run运行容器时,使用了 Dockerfile中的 CMD命令相同的命令。就会覆盖 DockerfileCMD命令。

我们来举个🌰:

还是我们之前的项目,我们在 Dockfile文件中使用如下的命令:

1
CMD ["/bin/bash"]

我们使用docker build -t dockerfile-example .构建一个新的镜像,镜像的名字叫:dockerfile-example,构建完成之后, 我们使用这个镜像运行一个容器,运行效果如下:

1
2
docker run -t dockerfile-example
root@cfc0384da571:/#

那么容器终端将会使用 shell 。说明 Dockfile文件中的CMD起作用了。

但是我们不想使用 Dockfile文件中的CMD指定的命令。我们可以:

1
2
3
docker run -t dockerfile-example /bin/ps
PID TTY TIME CMD
1 pts/0 00:00:00 ps

这时,docker run结尾指定的/bin/ps命令覆盖了DockerfileCMD中指定的命令。

ENTRYPOINT

ENTRYPOINTCMD类似。

ENTRYPOINT有两种模式:

1
2
ENTRYPOINT <command> (shell模式)
ENTRYPOINT [ "executable", "param1", "param2" ] (exec模式)

但是ENTRYPOINT不会被 docker run中执行的命令覆盖,并且docker run 命令中指定的任何参数都会被当成参数再次传递给 ENTRYPOINT。如果想要覆盖 ENTRYPOINT,则需要在docker run中指定 –entrypoint选项。 Dockerfile中只允许有一个ENTRYPOINT,多指定时会覆盖前端的设置的ENTRYPOINT`指令。 而只执行最后的 ENTRYPOINT指令。

举个例子:

我们重写我们的Dockerfile文件。添加 ENTRYPOINT指令:

1
2
3
4
5
6
7
8
9
# Version: 0.0.3
FROM ubuntu:16.04
MAINTAINER 何民三 "cn.liuht@gmail.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hello World, 我是个容器' \
> /var/www/html/index.html
ENTRYPOINT ["/usr/sbin/nginx"]
EXPOSE 80

执行 docker image build -t test2:0.0.1 .构建我们的镜像、

构建完成之后, 启动一个容器:docker run -i -t test2:0.0.1 -g “daemon off;” ``

在运行容器时,我们使用了-g "daemon off;". 这个参数会传递给 ENTRYPOINT。最终在容器中执行的命令为/usr/sbin/nginx -g "daemon off;"

LABEL

LABEL用于为镜像添加元数据,元数据以键值对的形式指定:

1
LABEL <key>=<value> <key>=<value> <key>=<value>

使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。

如,通过LABEL指定一些元数据:

1
LABEL name="shuliqi" age="23"

构建容器完后才能之后, 可以使用docker inspect查看

1
docker inspect test3:0.0.1

注意:Dockerfile中还有个 MAINTAINER用来指定镜像的作者。但是``MAINTAINER并不推荐使用,更推荐使用LATER`来指定镜像作者。如:

1
LABEL maintainer="itbilu.com"

EXPOSE

EXPOSE用来指定容器在运行的时监听的端口。

格式如下:

1
EXPOSE <port> [<port> ...]
1
EXPOSE 3000/tcp

注意:EXPOSE并不会让容器的端口访问到主机。要使其可以访问, 需要在docker run运行容器时通过 -p来发布这些端口。如:

1
docker run -t -p:8000:3000 express-for-docker:0.0.1

ENV

ENV用于设置环境变量,有以下两种形式:

1
2
ENV <key> <value>
ENV <key>=<value> ...

例如:

1
ENV SHULIQI_PATH=/home/shuliqi/

设置完, 这个环境变量在 ENV命令之后都可以使用。如:

1
2
ENV SHULIQI_PATH=/home/shuliqi/
WORKDIR $SHULIQI_PATH

这个环境变量不仅可以在构建镜像中使用,使用该镜像创建的容器也可以使用。

ADD

ADD指令用于复制构建环境中的文件/目录到镜像中。

有两种使用格式:

1
2
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]

通过ADD复制文件时,需要通过指定源文件位置,并通过<dest>来指定目标位置。可以是一个构建上下文中的文件或目录,也可以是一个URL,但不能访问构建上下文之外的文件或目录。

如,通过ADD复制一个网络文件:

1
ADD http://wordpress.org/latest.zip $ITBILU_PATH

在上例中,$ITBILU_PATH是我们使用ENV指定的一个环境变量。

另外,如果使用的是本地归档文件(gzipbzip2xz)时,Docker会自动进行解包操作,类似使用tar -x

COPY

COPY同样用于复制构建环境中的文件或目录到镜像中。其有以下两种使用方式:

1
2
COPY <src>... <dest>
COPY ["<src>",... "<dest>"]

COPY指令非常类似于ADD,不同点在于COPY只会复制构建目录下的文件,不能使用URL也不会进行解压操作。

VOLUME

VOLUME用于创建挂载点,即向所构建镜像创使的容器添加卷

格式:

1
VOLUME ["/data"]

一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:

  • 卷可以容器间共享和重用
  • 容器并不一定要和其它容器共享卷
  • 修改卷后会立即生效
  • 对卷的修改不会对镜像产生影响
  • 卷会一直存在,直到没有任何容器在使用它

VOLUME让我们可以将源代码、数据或其它内容添加到镜像中,而又不并提交到镜像中,并使我们可以多个容器间共享这些内容。

如,通过VOLUME创建一个挂载点:

1
2
3
4
5
6
7
8
9
FROM node:12.17.0
COPY . /shuliqi
WORKDIR /shuliqi
RUN ["npm", "install"]
EXPOSE 3000/tcp
CMD ["/bin/bash"]
# `VOLUME`创建一个挂载点
ENV SHULIQI_PATH /myblog/
VOLUME [$SHULIQI_PATH]

构建的镜像,并指定镜像名为express-for-docker。构建镜像后,使用新构建的运行一个容器。运行容器时,需-v参将能本地目录绑定到容器的卷(挂载点)上,以使容器可以访问宿主机的数据。

1
2
3
4
$ docker run -i -t -v ~/myblog:/myblog/  express-for-docker:0.0.1
root@31b0fac536c4:/# cd /myblog/
root@31b0fac536c4:/myblog# ls
blog

如上所示,我们已经可以容器的/home/myblog/目录下访问到宿主机`~/myblog目录下的数据了。

USER

USER用于指定运行镜像所使用的用户:

1
USER daemon

使用USER指定用户时,可以使用用户名、UIDGID,或是两者的组合。以下都是合法的指定试:

1
2
3
4
5
6
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

使用USER指定用户后,Dockerfile中其后的命令RUNCMDENTRYPOINT都将使用该用户。镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。

WORKDIR

WORKDIR用于在容器内设置一个工作目录:

1
WORKDIR /path/to/workdir

通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUNCMDENTRYPOINTADDCOPY等命令都会在该目录下执行。

如,使用WORKDIR设置工作目录:

1
2
3
4
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

在以上示例中,pwd最终将会在/a/b/c目录中执行。

在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。

ARG

ARG用于指定传递给构建运行时的变量:

1
ARG <name>[=<default value>]

如,通过ARG指定两个变量:

1
2
ARG site
ARG build_user=舒丽琦

以上我们指定了sitebuild_user两个变量,其中build_user指定了默认值。在使用docker build构建镜像时,可以通过--build-arg <varname>=<value>参数来指定或重设置这些变量的值。

1
docker build --build-arg site=shuliqi.github.io -t express-for-docker .

这样我们构建了 express-for-docker 镜像,其中site会被设置为 shuliqi.github.io,由于没有指定build_user,其值将是默认值舒丽琦

ONBUILD

ONBUILD用于设置镜像触发器:

1
ONBUILD [INSTRUCTION]

当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被钥触发。

如,当镜像被使用时,可能需要做一些处理:

1
2
3
4
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

STOPSIGNAL

STOPSIGNAL用于设置停止容器所要发送的系统调用信号:

1
STOPSIGNAL signal

所使用的信号必须是内核系统调用表中的合法的值,如:9SIGKILL

SHELL

SHELL用于设置执行命令(shell式)所使用的的默认shell类型:

1
SHELL ["executable", "parameters"]

SHELL在Windows环境下比较有用,Windows下通常会有cmdpowershell两种shell,可能还会有sh。这时就可以通过SHELL来指定所使用的shell类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S"", "/C"]
RUN echo hello
文章作者: 舒小琦
文章链接: https://shuliqi.github.io/2020/10/20/Dockerfile文件的使用/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 舒小琦的Blog