Docker:镜像构建 DockerFile
Docker:镜像构建 DockerFile
- 镜像构建
- docker build
- Dockerfile
- FROM
- COPY
- ENV
- WORKDIR
- ADD
- RUN
- CMD
- ENTRYPOINT
- USER
- ARG
- VOLUME
镜像构建
在Docker
官方提供的镜像中,大部分都是基础镜像,他们只提供某个简单的功能,如果想要一个功能更加丰富的镜像,就需要自己制作。
比如说一个容器配置完毕后,想要让容器便于传输,就可以封装为一个镜像。或者说希望让自己的容器可以被别人看到,提交到仓库上去,也要先变为镜像。
在Docker
指令中,docker commit
可以使用快照的形式快速制作一个镜像,它直接将一个容器导出为镜像。除此之外,Docker
还提供了另一种方式构建镜像:编写Dockerfile
。
Dockerfile
是一个文件,首字母大小写任意,依据这个文件,就可以构建出一个镜像。在联网的状态下,只要有这个文件,就可以构建出任意的镜像。
docker build
docker bild
命令可以读取Dockerfile
文件,并依据文件构建镜像。
语法:
docker build [option] path
参数:
-f
:指定要使用的Dockerfile
路径,默认为当前目录下的Dockerfile
文件-t
:指定镜像的名称与标签
具体使用,在稍后Dockerfile
编写时一起讲解。
Dockerfile
Dockerfile
是一个文本文件,内部包含多条指令,这些指令描述了如何构建一个镜像,如果构建的镜像不符合要求,还可以修改Dockerfile
反复制作镜像。
Dockerfile
的不区分大小写,后续的指令都以大写形式。
Dockerfile
使用#
进行注释:
# 这是一行注释
FROM
功能
:指定一个基础镜像
语法:
FROM image[:tag] [AS name]
指定镜像时,可以使用as
对这个镜像重命名,这样可以在一个DockerFile
中进行多级构建,这个稍后会讲解。
示例:
FROM ubuntu:22.04 AS ubt1
FROM ubuntu:22.04 AS ubt2
使用FROM
指定基础镜像时,如果基础镜像不存在,那么会自动拉取。
COPY
功能
:从宿主机或者其它镜像中拷贝文件
语法:
COPY [option] src[,src] dst
COPY [option] "src"[,"src"] "dst"
将文件从src
拷贝到dst
,如果有多个文件,使用逗号分隔。如果在文件名中没有出现空格,可以不用双引号,如果文件名内有空格,就需要使用"src"
和"dst"
。
选项:
--chown
:修改用户和组--from
:可以从之前的镜像中拷贝文件
拷贝宿主机文件:
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
文件,这是构建镜像时拷贝进去的。
除此之外,还可以进行多级构建,所谓的多级构建,就是可能最终镜像内部的文件来自不同环境。那么先在某些镜像环境内部生成所需的文件,再把文件拷贝到最终的镜像内。
示例:
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
功能
:设置环境变量
语法:
ENV name=value
环境变量不仅可以在容器内部使用,还可以在后文通过${}
引用。
示例:
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
功能
:修改工作目录
语法:
WORKDIR path
在构建镜像时,默认的工作目录都是/
根目录,如果想要切换目录,可以使用WORKDIR
。
示例:
FROM nginx
WORKDIR /usr/share/nginx/html
COPY ./test.txt ./
以上代码,把宿主机的./test.txt
文件拷贝到容器的/usr/share/nginx/html
目录下。
因为修改了WORKDIR
,所以./
就是/usr/share/nginx/html
。
ADD
功能
:将文件添加到镜像中,可以解压缩tar压缩文件
语法:
ADD src dst
选项:
--chown
:修改文件所有者和组
此处的COPY
非常类似COPY
,用法也是一致的,功能都是拷贝文件。
但是ADD
比COPY
更强大,如果src
是压缩包,那么会自动完成解压缩。如果src
是一个url
,还会完成自动下载。
示例:
FROM ubuntu:22.04
ADD ./test.tar /
将test.tar
文件,通过ADD
命令,添加到镜像的根目录中。
输出结果:
创建完镜像再启动后,根目录下的内容不是test.tar
而是test.txt
,说明文件被自动解压了。
RUN
功能
:在构建镜像的过程中执行命令
语法:
RUN command
RUN ["command", "arg1", "arg2",...]
在构建镜像的过程中,可以通过RUN
执行指定命令,两种语法中,他们的效果其实是不一样的。
直接RUN command
,会以/bin/sh -c
来执行指令,这可以提供一些bash
的特性,比如可以使用通配符? *
等进行替换,以及运行.sh
程序等。
但是使用[]
的形式执行命令,不会具有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
功能
:指定容器启动时执行的命令
语法:
CMD ["command","arg1","arg2",...]
CMD command arg1 arg2 ...
其中CMD command
和CMD []
的两种形式,和之前的RUN
一样,重点在于是否具有shell
特性。
示例:
FROM ubuntu:22.04
CMD ["echo", "hello world"]
这个镜像,在启动时会执行CMD
内的命令,输出hello world
字符串。
原先ubuntu
的CMD
是bash
,也就是进入命令行,由于输出字符串的命令将其覆盖了,所以无法直接进入命令行。
除此之外,CMD
的命令还会进行覆盖,比如Dockerfile
内部的多个CMD
,后面的会覆盖前面的:
FROM ubuntu:22.04
CMD ["echo", "hello world"]
CMD ["echo", "hello C++"]
CMD ["echo", "hello Docker"]
最后该镜像的命令是echo “hello Docker”
,前两个被覆盖了。
除此之外,在启动容器时用户也可以指定命令,这个命令也可以覆盖CMD
:
ENTRYPOINT
功能
:指定容器启动时执行的命令
语法:
ENTRYPOINT ["command", "arg1", "arg2",...]
ENTRYPOINT command arg1 arg2 ...
ENTRYPOINT
和CMD
的功能是一样的,但是语法特性略有差别。
在CMD
中,后面的CMD
会覆盖前面的CMD
,启动容器时的命令也会覆盖CMD
。
在ENTRYPOINT
中,一个Dockerfile
只有最后一个ENTRYPOINT
生效,但是用户输入命令时,会变成ENTRYPOINT
的参数,而不是覆盖。
示例:
FROM ubuntu:22.04
ENTRYPOINT ["echo", "hello world"]
构建成功后,在启动容器时指定命令echo "hello Docker"
,输出结果却不是hello Docker
,而是:
hello world echo hello Docker
这是因为后面的echo "hello Docker"
都变成了ENTRYPOINT
内部的指令的参数,最后相当于执行:
echo "hello world" "echo" "hello Docker"
USER
功能
:指定运行容器时的用户或用户ID
语法:
USER user[:group]
默认情况下用户为root
,可以通过USER
命令修改后文执行指令时的用户。
示例:
FROM ubuntu:22.04
RUN useradd new_usr
USER new_usr
WORKDIR /home/new_usr
以上代码,通过RUN
创建了一个new_usr
用户,并切换用户为new_usr
。
输出结果:
创建容器后,默认用户就是new_usr
,并且处于该用户的家目录中。
ARG
- 功能:定义构建时的变量
语法:
ARG name[=value]
这个用于指定一些参数,这个参数可以在Dockerfile
中通过${}
引用。
示例:
FROM ubuntu:22.04
ARG path=/home/new_usr
RUN useradd new_usr
USER new_usr
WORKDIR ${path}
将刚才的用户家目录定义在参数path
中,后续可以直接通过${path}
引用。
VOLUME
- 功能:创建一个匿名卷,并指定挂载点
语法:
VOLUME ["path"]
VOLUME path
由于镜像实例化时,用户所处的路径是不确定的,就算确定了路径,也不保证用户存在这个路径,所以在镜像构建阶段不能创建绑定卷,只能创建匿名卷。
在VOLUME
的参数中,指定的path
就是要进行绑定的匿名卷,可以持久化一些重要数据,就算容器崩溃,用户也有机会找回数据。