一、什么是 dockerfile
首先来看一下官方介绍
画红框中文字的意思是:我们可以按照需要通过 dockerfile 来构建运行时候所需要的环境。
通过上文了解之后,我们可以知道,通过 dockerfile 可以构建属于我们自己的镜像,这样一来就省去了以前那种方式了。好比我们现在想要构建一个我们自己的 tomcat 服务器,并且让里面跑我们所需要的程序,按照以前的方式,我们需要拉取 tomcat 镜像,然后启动镜像后,将 war 上传至容器,然后再重启容器,这样一来是很麻烦的。而现在有了 dockerfile 之后,我们可以直接自己编写镜像,而且这样一来可复用性也提高很多。
二、Dockerfile 常用指令
官方文档:Dockerfile reference | Docker Documentation
1. FROM
FROM 指令初始化一个新的构建阶段,并为后续指令设置基本镜像。一般用于构建 image 时指定基础镜像。
格式:
FROM xxximage
FROM image:版本
举例:
FROM centos
这里有一点注意,很多博客都说 FROM 必须是 dockerfile 第一个指令,其实这个是错误的,来看官网原文档的叙述:
ARG 指令可以写在 FROM 前面,作为第一个指令。
2. RUN
RUN 指令可以在构建 dockerfile 时运行一些命令,可以写多条。
格式:
RUN shell脚本
RUN ["可执行文件", "参数1", "参数2", ...]
举例:
RUN yum -y install net-tools
RUN yum -y install vim
RUN ["./test.php", "dev", "offline"] 该命令等价于 RUN ./test.php dev offline
注意点:
- 在下一次构造 dockerfile 时,RUN 指令的缓存不会自动失效。像 RUN apt-get dist-upgrade-y 这样的指令的缓存将在下一次构建期间重用。可以使用 -- no-cache 标志使 RUN 指令的缓存失效,例如 docker build -- no-cache。
- Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大。
举例:
FROM xxx
RUN xxx1
RUN xxx2
RUN xxx3
以上 dockerfile 如果在被构建时会创建 3 层镜像。
改进:
FROM xxx
RUN xxx1 \
&& xxx2 \
&& xxx3
我们将多个 RUN 命令使用 && 进行连接,这样再进行构建 dockerfile 时,就只会创建 1 层。\ 为换行符
3. CMD
CMD 的主要目的是为正在执行的容器提供缺省值。这些缺省值可以包括一个可执行文件,也可以省略可执行文件,在这种情况下,还必须指定 ENTRYPOINT 指令。
一个 Dockerfile 中只能有一条 CMD 指令。如果在一个 dockerfile 中出现了多个 CMD,那么只有最后一个 CMD 才会生效。
格式:
CMD ["可执行文件", "参数1", "参数2", ...] (执行可执行文件,优先)
CMD ["参数1", "参数2", ...] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
CMD shell脚本 (执行shell内部命令)
举例:
CMD /bin/bash
CMD ["/home/apache-tomcat-8.5.37/bin/catalina.sh","run"]
注意点:
- CMD 不同于 RUN,CMD 用于指定在容器启动时所要执行的命令,而 RUN 用于指定镜像构建时所要执行的命令。
- 如果用户在
docker run
中指定了参数,那么它们将覆盖 CMD 中指定的缺省值。
4. LABEL
LABEL 指令向映像添加元数据。LABEL 添加的数据为 k-v 类型的数据,同一个 dockerfile 可以添加多个 LABEL。
格式:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
举例:
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL description="This text illustrates \
that label-values can span multiple lines."
LABEL multi.label1="value1" multi.label2="value2" other="value3"
(上面例子中有一个用到了 \ ,作用和上面 RUN 的一样,换行)
上面的这些元数据可以通过 docker image inspect 命令进行查看,或者可以增加 --format 进行具体的查看
docker image inspect --format='' 镜像id 或 镜像:版本
5. MAINTAINER(已废弃)
MAINTAINER 指令可以设置生成镜像的作者字段。
格式:
MAINTAINER 作者名称
举例:
MAINTAINER lemon1234
MAINTAINER apache
但是这个命令已经被废弃掉了,大家如果想增加作者信息啥的,可以使用 LABEL。
6. EXPOSE
EXPOSE 指令通知 Docker 容器在运行时监听指定的网络端口。我们可以指定端口是否侦听 TCP 或 UDP,如果没有指定协议,则默认为 TCP。
格式:
EXPOSE <port> [<port>/<protocol>...]
举例:
EXPOSE 80
EXPOSE 80/udp
EXPOSE 指令实际上并不发布端口。它作为一种文档,给构建 dockerfile 的人和使用镜像的人去看的。所以不管 EXPOSE 设置如何,我们都可以在运行时使用 -p 标志覆盖它们。
7. ENV
ENV 指令用于设置环境变量的值,存储方式为 k-v 形式。
格式:
ENV key1=value1 key2=value2 ...
ENV key1 value1
举例:
ENV name=lemon123
ENV age=15 phone=18512341234
ENV company xxxx有限责任公司
通过 ENV 设置的环境变量,我们也可以通过 docker inspect 查看这些内容,而且启动的时候还可以通过 docker run --env key=value 改变他们。
注意点:
- 当镜像被运行生成容器后,这些被 ENV 定义的环境变量是保持不变的。
8. ADD
ADD 指令用来复制文件、目录、远程文件 URL 到镜像的文件系统。
格式:
ADD [--chown=<user>:<group>] 要复制的东西.... 镜像中的地址
ADD [--chown=<user>:<group>] ["要复制的东西",... "镜像中的地址"]
举例:
ADD apache-tomcat-8.5.37.tar.gz /home/
ADD http://example.com/foobar /
注意点:
- --chown=<user>:<group> 这玩意只支持 linux,windows 是不支持的。
- 要复制的东西的路径必须位于构建的上下文中,不能 ADD ../xxx.xx /home 这样的出现。所以一般我都是将 dockerfile 和要 ADD 的东西扔在一个地方。
- ADD 还支持通配符,类似 ADD hom* /mydir/,可以将宿主机当前名字开头是 hom 的文件都复制到 /mydir 下面。详细通配符的介绍可以去看 golang 的 filepath.Match 方法。
- 镜像中的地址是绝对路径,或者相对于 WORKDIR 的路径,源文件将被复制到目标容器中。
- 如果要复制的东西是目录,ADD 指令不会复制目录本身,只复制其内容。
- 如果 ADD 是一个压缩文件,在复制到镜像中,会自动解压;但如果要复制的东西是一个压缩包 URL,下载之后是不会自动解压的。
更详细的 ADD 建议去看 dockerfile 的官方文档:Dockerfile reference | Docker Documentation
9. COPY
COPY 指令其实和 ADD 类似,但是 COPY 仅仅之后拷贝。
格式:
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
举例:
COPY apache-jmeter-5.4.1.tgz /jmeterdocker
COPY config.py /
10. ENTRYPOINT
ENTRYPOINT 指令与 CMD 相似,但是却又有这一定的区别。
格式:
ENTRYPOINT ["可执行文件", "参数1", "参数2", ...]
ENTRYPOINT shell脚本
举例:
ENTRYPOINT ["/docker-entrypoint.sh"]
ENTRYPOINT docker-entrypoint.sh
见过格式之后,我们再来说区别,这里通过一个例子来说明:
首先来看 CMD 指令,这个指令在一个 dockerfile 中可以写多个,但是执行的只有最后一个,好比 tomcat 镜像最后一个 CMD
CMD ["catalina.sh" "run"]
如果我们在启动镜像的时候,在命令最后面加上其他的参数(这样做可以覆盖 CMD 要执行的命令)
这里我在启动的时候增加了一个参数:ls -h 的 shell 命令,然后我们可以看到,下面我们将 tomcat 的目录查了出来。而这样的 tomcat 其实是没有启动成功的。
接着我们再来看一下 nginx 的 dockerfile
可以看到,nginx 的 dockerfile 是先有一个 ENTRYPOINT,接着下面跟着一个 CMD。如果我们去启动 nginx,那么 nginx 镜像中会执行 docker-entrypoint.sh nginx -g daemon off;
如果我们将 nginx 的启动扔在 ENTRYPOINT 中执行,这样就不怕用户在最后修改 CMD 的命令了(虽然 ENTRYPOINT 也可以修改~~~)。
11. VOLUME
VOLUME 指令创建一个具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷。
格式:
VOLUME ["/data", "/xxx", ...]
VOLUME "/var/www", "/var/log/apache2", ...
举例:
VOLUME ["/var", "/home"]
VOLUME "/var", "/home"
这里我们使用 VOLUME 是为了定义一个容器卷。这里可能会有人问了,之前学习的 docker run -v 是什么,和这里的 VOLUME 有什么联系?
首先来说 docker run -v 这个命令,它是用来挂载宿主机目录和容器目录的,挂载之后的文件,可以在宿主机看到,而且还会实时同步。
接着再来说 VOLUME 指令,这个指令其实就是指定一个匿名卷,指定好这个卷后,docker 会将这个卷自动绑定到宿主机的一个地方(不同版本的 docker 可能不一样),我现在绑定的地方在:/var/lib/docker/volumes/{容器ID},小伙伴们可以自己去查看一下。
那为啥我们要弄 VOLUME,这里再来举个例子。好比 MySQL 这个镜像,我们通过镜像生成了容器,现在我们如果将这个 MySQL 的容器删除,要是没有 VOLUME,那么我们删除容器之后,会连带 MySQL 的数据一起删除,而且还是永久性删除。但是如果有了 VOLUEM,及时删除了容器,我们还可以通过之前的容器 ID 找到默认绑定到宿主机的位置进行恢复数据。
VOLUME 相当于是指定了一个默认的卷,而 docker run -v 是用户自定义指定一个卷。
那为啥不在 dockerfile 中提供一个可以直接映射宿主机目录和容器目录的指令呢?很简单,如果真的有这个指令,那么 dockerfile 的可移植性就变差了,毕竟谁能保证我们每个人所需要映射的目录是一样的?所以最好的办法是将这个映射的决定扔给运行这个镜像的人。
12. USER
USER 指令设置用户名(或 UID)和可选的用户组(或 GID),以在运行映像时以及 Dockerfile 中跟随它的任何 RUN、CMD 和 ENTRYPOINT 指令使用。
格式:
USER <user>[:<group>]
USER <UID>[:<GID>]
举例:
USER lemon1234
USER patrick
注意点:
- 其中用户名或 ID 是指可以在容器基础镜像中找到的用户。如果在容器基础镜像中没有创建特定用户,则在 USER 指令之前添加 useradd 命令以添加特定用户。
- 使用 USER 指定用户后,Dockerfile 中后续的命令 RUN、CMD、ENTRYPOINT 都将使用该用户。
Linux 添加用户:RUN useradd -d /home/username -m -s /bin/bash username USER username
Windows 添加用户:RUN net user /add patrick
13. WORKDIR
WORKDIR 指令为 Dockerfile 中任何 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指令设置工作目录。
格式:
WORKDIR /path/to/workdir
举例:
WORKDIR /home
WORKDIR /var/local
这个 WORKDIR 类似于 cd 命令。
注意点:
- 如果 dockerfile 中增加了一个 WORKDIR /a,那么此时工作目录为 /a,然后再增加一个 WORKDIR b,那么此时工作目录为 /a/b。所以这里建议最好使用绝对路径。
- 如果工作目录不存在,则Docker Daemon会自动创建
WORKDIR 还可以解析环境变量,例如:
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
这时输出的是 /path/$DIRNAME
14. ARG
ARG 指令定义了一个变量,用户可以在构建时通过 docker build 命令使用 --build-arg <varname>=<value> 标志将其传递给构建器。
格式:
ARG <name>[=<default value>]
举例:
ARG site
ARG build_user=www
注意点:
- 不建议使用构建时变量来传递 github 密钥、用户凭据等机密信息。使用 docker history 命令的图像的任何用户都可以看到构建时变量值。
- 如果 ARG 指令具有默认值,并且在构建时没有传递任何值,则构建器将使用默认值。
- ARG 变量定义从 Dockerfile 中定义它的行开始生效,而不是从命令行或其他地方的参数使用开始。
这里主要说一下 ARG 的作用域。
如果在第一个 FROM 之前定义的 ARG,那么可以在下面任何 FROM 去使用;如果是在 FROM 里面定义的,就不行了,如果需要使用,需要在每一个 FROM 中进行定义。
FROM centos
RUN echo $user
ARG user
RUN echo $user
来看这个 dockerfile,第二句 RUN echo $user 一定是啥也输出不出来的。
15. ONBUILD
ONBUILD 指令向镜像添加了一条触发指令,该指令将在稍后的时间执行,此时镜像用作另一个构建的基础。
格式:
ONBUILD <INSTRUCTION>
举例:
ONBUILD ADD . /app/src
ONBUILD RUN yum -y install vim
这个 ONBUILD 一般是用在父镜像中,子镜像继承父镜像后,当我们构建子镜像时,会触发父镜像的 ONBUILD,这样就省的子镜像再去写了。
16. SHELL
格式:
SHELL ["executable", "parameters"]
举例:
SHELL ["/bin/sh", "-c"]
SHELL ["cmd", "/S", "/C"]
这个比较简单,SHELL 就是用来去执行一些 SHELL 脚本,自己可以去学学 shell 脚本,然后自己去用用这个命令。
三、构建 dockerfile
上面我们讲了很多 dockerfile 的指令,接下来看一下,有了 dockerfile,如何通过 dockerfile 生成镜像。
docker build 参数... PATH | URL | -
这里参数有点多,我们主要说几个常用的参数。
-f:指定要使用的 dockerfile 路径;
--build-arg=[] :设置镜像创建时的变量;
--tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
下面来一个简单的实例~
自己在 /home 下面创建一个 dockerfile 的文件夹,然后在文件夹里面创建一个 dockerfile 的文件。
touch dockerfile;
接着编辑一下 dockerfile 文件。
vi dockerfile;
FROM centos
RUN echo $user
ARG user
RUN echo $user
保存退出编辑模式,因为这个 dockerfile 需要我们传递一个 user 的参数,所以这里我们就用到了 --build-arg 这个参数,详细来看一下具体命令。
docker build --build-arg user=lemon1234 -f dockerfile -t dockerfile/test:1.0 .
这里有两个地方需要注意一下,一个是在这个命令最后面是有一个 . 的,千万别漏了;还有一个是我执行这个命令的时候,目录的位置是在 /home/dockerfile 下,所以 -f 后面直接写 dockerfile 即可。
可以看到,是没有问题,接着我们来看一下我们刚刚生成的镜像~