Docker:镜像构建 DockerFile
镜像构建
在Docker官方提供的镜像中,大部分都是基础镜像,他们只提供某个简单的功能,如果想要一个功能更加丰富的镜像,就需要自己制作。
比如说一个容器配置完毕后,想要让容器便于传输,就可以封装为一个镜像。或者说希望让自己的容器可以被别人看到,提交到仓库上去,也要先变为镜像。
在Docker指令中,docker commit可以使用快照的形式快速制作一个镜像,它直接将一个容器导出为镜像。除此之外,Docker还提供了另一种方式构建镜像:编写Dockerfile。
Dockerfile是一个文件,首字母大小写任意,依据这个文件,就可以构建出一个镜像。在联网的状态下,只要有这个文件,就可以构建出任意的镜像。
docker build
docker bild命令可以读取Dockerfile文件,并依据文件构建镜像。
语法:
bash
docker build [option] path
参数:
-f:指定要使用的Dockerfile路径,默认为当前目录下的Dockerfile文件-t:指定镜像的名称与标签
具体使用,在稍后Dockerfile编写时一起讲解。
Dockerfile
Dockerfile是一个文本文件,内部包含多条指令,这些指令描述了如何构建一个镜像,如果构建的镜像不符合要求,还可以修改Dockerfile反复制作镜像。
Dockerfile的不区分大小写,后续的指令都以大写形式。
Dockerfile使用#进行注释:
bash
# 这是一行注释
FROM
功能:指定一个基础镜像
语法:
bash
FROM image[:tag] [AS name]
指定镜像时,可以使用as对这个镜像重命名,这样可以在一个DockerFile中进行多级构建,这个稍后会讲解。
示例:
bash
FROM ubuntu:22.04 AS ubt1
FROM ubuntu:22.04 AS ubt2
使用FROM指定基础镜像时,如果基础镜像不存在,那么会自动拉取。
COPY
功能:从宿主机或者其它镜像中拷贝文件
语法:
bash
COPY [option] src[,src] dst
COPY [option] "src"[,"src"] "dst"
将文件从src拷贝到dst,如果有多个文件,使用逗号分隔。如果在文件名中没有出现空格,可以不用双引号,如果文件名内有空格,就需要使用"src"和"dst"。
选项:
--chown:修改用户和组--from:可以从之前的镜像中拷贝文件
拷贝宿主机文件:
bash
FROM ubuntu:22.04
COPY ./test.txt /
以上代码指定了一个ubuntu的基础镜像,并拷贝一个宿主机文件test.txt到根目录下。
通过docker build构建镜像:

选项-t指定镜像名为my-ubuntu:v1,随后开始执行Dockerfile内部的指令,可以看到[2/2]COPY ./test.txt /,这就是之前写的COPY指令。
实例化一个容器:

进入容器后,根目录就多出了test.txt文件,这是构建镜像时拷贝进去的。
除此之外,还可以进行多级构建,所谓的多级构建,就是可能最终镜像内部的文件来自不同环境。那么先在某些镜像环境内部生成所需的文件,再把文件拷贝到最终的镜像内。
示例:
bash
FROM nginx AS build-stage
FROM ubuntu
COPY --from=build-stage /usr/share/nginx/html /
以上代码,先创建了一个nginx镜像,重命名为build-stage,随后创建一个ubuntu镜像,在ubuntu镜像中,拷贝来自build-stage的内容,把目录/usr/share/nginx/html下的文件拷贝到自己的根目录。
多级构建时,最终的镜像是最后一个FROM指定的镜像,前面指定的镜像都是为了生成某些文件。
构建镜像:

最终生成一个my-ubuntu:v2镜像。
进入镜像:

进入后,根目录多出了index.html,输出后得到一个Welcome to nginx!的网页文件,这个文件就是在nginx镜像生成的,最后拷贝到了ubuntu中。
ENV
功能:设置环境变量
语法:
bash
ENV name=value
环境变量不仅可以在容器内部使用,还可以在后文通过${}引用。
示例:
bash
FROM nginx AS build-stage
FROM ubuntu
ENV ngx_path=/usr/share/nginx/html
COPY --from=build-stage ${ngx_path} /
定义了一个环境变量ngx_path,后续可以直接通过${ngx_path}取出变量值。
WORKDIR
功能:修改工作目录
语法:
bash
WORKDIR path
在构建镜像时,默认的工作目录都是/根目录,如果想要切换目录,可以使用WORKDIR。
示例:
bash
FROM nginx
WORKDIR /usr/share/nginx/html
COPY ./test.txt ./
以上代码,把宿主机的./test.txt文件拷贝到容器的/usr/share/nginx/html目录下。
因为修改了WORKDIR,所以./就是/usr/share/nginx/html。
ADD
功能:将文件添加到镜像中,可以解压缩tar压缩文件
语法:
bash
ADD src dst
选项:
--chown:修改文件所有者和组
此处的COPY非常类似COPY,用法也是一致的,功能都是拷贝文件。
但是ADD比COPY更强大,如果src是压缩包,那么会自动完成解压缩。如果src是一个url,还会完成自动下载。
示例:
bash
FROM ubuntu:22.04
ADD ./test.tar /
将test.tar文件,通过ADD命令,添加到镜像的根目录中。
输出结果:

创建完镜像再启动后,根目录下的内容不是test.tar而是test.txt,说明文件被自动解压了。
RUN
功能:在构建镜像的过程中执行命令
语法:
bash
RUN command
RUN ["command", "arg1", "arg2",...]
在构建镜像的过程中,可以通过RUN执行指定命令,两种语法中,他们的效果其实是不一样的。
直接RUN command,会以/bin/sh -c来执行指令,这可以提供一些bash的特性,比如可以使用通配符? *等进行替换,以及运行.sh程序等。
但是使用[]的形式执行命令,不会具有bash特性。
示例:
bash
FROM ubuntu:22.04
COPY ./test* /
RUN mkdir dir1
RUN mkdir dir2
RUN cp ./test* dir1
RUN ["cp", "./test*", "dir2"]
以上代码,把宿主机的./test*拷贝到镜像的根目录,这是一个通配符,可以拷贝多个文件。
随后通过RUN执行mkdir命令,创建了两个目录。最后把从宿主机拷贝来的文件再拷贝到目录里面,分别使用RUN command和RUN []两种语法。
输出结果:

在当前目录下,有test、test.cpp、test.java、test.txt四个文件,构建镜像时,可以看到RUN cp ./test* dir1执行成功了,但是RUN ["cp", "./test*", "dir2"]失败了。
因为RUN []不支持bash特性,导致无法匹配./test*通配符,最后显示找不到./test*这个文件。
CMD
功能:指定容器启动时执行的命令
语法:
bash
CMD ["command","arg1","arg2",...]
CMD command arg1 arg2 ...
其中CMD command和CMD []的两种形式,和之前的RUN一样,重点在于是否具有shell
特性。
示例:
bash
FROM ubuntu:22.04
CMD ["echo", "hello world"]
这个镜像,在启动时会执行CMD内的命令,输出hello world字符串。

原先ubuntu的CMD是bash,也就是进入命令行,由于输出字符串的命令将其覆盖了,所以无法直接进入命令行。
除此之外,CMD的命令还会进行覆盖,比如Dockerfile内部的多个CMD,后面的会覆盖前面的:
bash
FROM ubuntu:22.04
CMD ["echo", "hello world"]
CMD ["echo", "hello C++"]
CMD ["echo", "hello Docker"]
最后该镜像的命令是echo "hello Docker",前两个被覆盖了。
除此之外,在启动容器时用户也可以指定命令,这个命令也可以覆盖CMD:

ENTRYPOINT
功能:指定容器启动时执行的命令
语法:
bash
ENTRYPOINT ["command", "arg1", "arg2",...]
ENTRYPOINT command arg1 arg2 ...
ENTRYPOINT和CMD的功能是一样的,但是语法特性略有差别。
在CMD中,后面的CMD会覆盖前面的CMD,启动容器时的命令也会覆盖CMD。
在ENTRYPOINT中,一个Dockerfile只有最后一个ENTRYPOINT生效,但是用户输入命令时,会变成ENTRYPOINT的参数,而不是覆盖。
示例:
bash
FROM ubuntu:22.04
ENTRYPOINT ["echo", "hello world"]

构建成功后,在启动容器时指定命令echo "hello Docker",输出结果却不是hello Docker,而是:
bash
hello world echo hello Docker
这是因为后面的echo "hello Docker"都变成了ENTRYPOINT内部的指令的参数,最后相当于执行:
bash
echo "hello world" "echo" "hello Docker"
USER
功能:指定运行容器时的用户或用户ID
语法:
bash
USER user[:group]
默认情况下用户为root,可以通过USER命令修改后文执行指令时的用户。
示例:
bash
FROM ubuntu:22.04
RUN useradd new_usr
USER new_usr
WORKDIR /home/new_usr
以上代码,通过RUN创建了一个new_usr用户,并切换用户为new_usr。
输出结果:

创建容器后,默认用户就是new_usr,并且处于该用户的家目录中。
ARG
- 功能:定义构建时的变量
语法:
bash
ARG name[=value]
这个用于指定一些参数,这个参数可以在Dockerfile中通过${}引用。
示例:
bash
FROM ubuntu:22.04
ARG path=/home/new_usr
RUN useradd new_usr
USER new_usr
WORKDIR ${path}
将刚才的用户家目录定义在参数path中,后续可以直接通过${path}引用。
VOLUME
- 功能:创建一个匿名卷,并指定挂载点
语法:
bash
VOLUME ["path"]
VOLUME path
由于镜像实例化时,用户所处的路径是不确定的,就算确定了路径,也不保证用户存在这个路径,所以在镜像构建阶段不能创建绑定卷,只能创建匿名卷。
在VOLUME的参数中,指定的path就是要进行绑定的匿名卷,可以持久化一些重要数据,就算容器崩溃,用户也有机会找回数据。