Docker是什么?Docker有什么用?
按照Docker官方的如下简介
Docker是一个能够提供开发,交付,运行程序的开放平台,Docker可以将应用程序和底层基础设施进行隔离,这样可以快速的进行软件交付;通过Docker,可以像管理应用程序一样管理基础设施,通过利用Docker用于快速交付、测试和部署代码的方法,我们可以显著的减少Coding到上线运行之间的延迟;
如果刚接触Docker概念的人,看了上面的简介可能还不知道它是用来干嘛的,其实简单的说,Docker提供了两个基本能力:
- 打包应用程序:
 - 提供一个松散隔离的环境称之为Container,用来运行程序;
 
Docker提供的隔离和安全性允许同时很多个Container同时运行在宿主机上(物理机或者VM),Container是一个很轻量的包含程序运行所需的集合;
1. Docker架构
Docker的架构是一个经典的C/S模型,Docker client直接和Docker daemon通过REST API通信,底层根据是否是本机会有UNIX sockets或者网络的选择。Docker的架构图如下:主要有一下几部分组成:
1.1. Docker daemon
Docker  daemon(dockerd)负责镜像的构建,运行和分发;dockerd监听client的API请求,然后对Docker对象进行管理,这些对象包括:images,containers,networks和volumes;
1.2. Docker client
Docker的client(docker)是用户和Docker daemon交互的主要途径;当执行命令docker run时,client会通过REST API接口将此命令发送给dockerd;Docker client可以和多个Docker daemon交互;
Docker的client和daemon可以本机部署,也可以分离部署;
1.3. Docker Registries
Docker的注册中心用来存储Docker的镜像,Docker Hub 是一个公共的注册中心,任何人都可以使用,默认配置下,Docker将会在这里寻找镜像;你也可以搭建自己私有的注册中心;
当执行docker pull或者docker run命令时,会从配置的注册中心拉取镜像;当执行docker push命令,会将Docker Host构建的本地镜像推送到配置的远端注册中心;
默认的Docker Registry是docker.io,且是不可修改的,原因可参考Docker的一个issue,大意为:不支持通过config的方式修改默认注册中心,是因为私有注册中心无法全部覆盖Docker安装过程中所需要的所有镜像;我们可以在需要从私有注册中心pull或者push的操作中带上registry的地址,例如:
1  | $ docker login https://<YOUR-DOMAIN>:8080  | 
1.4. Docker 对象
前面提到过,Docker将内部的资源定义成了不同的对象,包括:images,containers,networks,volumes,plugins等;
- Images
 
image即镜像,是一个只读模板,内容包含用于创建Docker container的指令;通常情况下,会基于其他镜像进行额外的定制化来构建我们自己的镜像;例如基于ubuntu镜像来构建自己的镜像,此镜像额外的安装apache web server和我们的app,以及相关配置,然后对此镜像进行部署运行;
- Containers
 
container即容器,是一个镜像的运行实例;可以通过docker API或者docker client对一个容器进行create,start,stop,move,delete等操作;可以为container连接多个网络,挂载存储设备,也可以根据container当前运行状态创建一个新的image;
宿主机上的多个containers是相对隔离的;这个隔离性是我们可以控制的,例如网络,存储,其他子系统;对于运行的container,当进行remove后,所有没有进行持久化的修改状态都会在remove后直接丢弃;
2. Docker基础之Image构建
Docker提供了两种方式来构建镜像:
- 通过 Dockerfile 自动构建镜像;
 - 通过容器操作,并执行 Commit 打包生成镜像;
 
2.1. 基于Dockerfile的镜像构建
Docker可以通过解析Dockerfile指令来进行Image的构建。Dockerfile是一个文本文件,包含了用于构建镜像和运行所需要的全部指令;Docker通过docker build命令来读取Dockerfile进行Image的构建;
镜像构建是在Docker daemon上进行的,docker build构建命令需要获得Dockerfile文件和相关上下文;然后将获取到的全部上下文发送给Docker daemon,由daemon负责镜像构建;
构建的上下文是由PATH或URL指定位置的一个文件列表集合;PATH变量是docker client本地的文件系统,URL是一个Git的仓库地址;构建上下文是递归处理的,所以PATH会包含其所有的子目录,URL包含会repo和submodules;所以一般都是将Dockerfile放入一个空目录中,只添加构建所有需要的文件;
不要将根目录
/作为PATH来进行构建,这将会把文件系统所有文件全部作为构建上下文传递给Docker daemon进行构建
下面看一下如何构建一个看可以运行的镜像,创建一个helloworld目录,目录内创建如下两个文件:
1  | helloworld/  | 
helloworld/helloworld为Go编译生成的可执行文件,运行时每秒往本地的helloworld.txt写入当前的时间戳。
Dockerfile内容如下:
1  | FROM ubuntu  | 
基于ubuntu镜像进行新的镜像打包,这里只是简单的把本地的可执行文件helloworld打入镜像,并在启动镜像时执行该文件;在开始构建helloworld镜像前,先看一下docker build 的语法,如下:
1  | docker build [OPTIONS] PATH | URL  | 
下面开始构建镜像,在helloworld目录下,执行如下docker build进行镜像构建:
1  | PATH为当前目录,默认使用当前目录下的Dockerfile  | 
由于使用的是公司网络,所以触发了docker hub对于匿名用户的pull限流,具体可参考官方限流说明和常见的解决方案;这里使用Amazon的公共注册中心,将Dockerfile 进行如下修改:
1  | #FROM ubuntu  | 
继续进行构建:
1  | $ docker build -t dockertest/helloworld .  | 
可以通过docker images查看本地构建的镜像:
1  | $docker images  | 
可以通过docker run启动容器运行对应的镜像,
1  | $docker run -d dockertest/helloworld:v1  | 
然后通过docker exec登录容器查看对应程序的运行情况,
1  | $docker exec -it 676c35929eed bash  | 
2.2. Dockerfile语法
本节的Dockerfile语法主要是参考官方手册,在其基础上进行了一些总结和注意事项,下面进入整体:
Dockerfile指令文本的语法格式如下:
1  | # Comment  | 
Dockerfile的每一条命令由:指令和参数两部分组成;指令不区分大小写,但是习惯上我们会用大写的指令,以和参数部分进行区分;Docker按照Dockerfile的指令书写顺序依次进行解析和执行;
Dockerfile的第一条构建指令必须是FROM指令(除了以下三种特例);FROM指令用来指定构建本Image所需要依赖的父Image;FROM指令的前面可以存在三类语句:
- 解析器指令(parser directives);
 - 注释;
 - 全局参数ARG指令;
 
2.2.1. 注释
Docker中除了解析器指令外,所有以#开头的行都被认为是注释行;但其他位置的#会被认为是指令参数的一部分,不会被认为是注视;如下示例,RUN指令会完整的输出后面所有的字符串;
1  | # this is a Comment  | 
执行结果为:
1  | Step 2/4 : RUN echo 'we are running some # of cool things'  | 
每条Dockerfile指令执行的时候都会先移除含有的注释,如下两条语句是等价的:
1  | RUN echo hello \  | 
1  | RUN echo hello \  | 
对于注释行需要注意的是,任何前导空白符都会被忽略,关于前导空白符的说明如下:
任何注释或者指令行前面的空白符都会被忽略,如下:
 1
2
3 # this is a comment-line
RUN echo hello
>RUN echo world等价于
 1
2
3 ># this is a comment-line
>RUN echo hello
>RUN echo world但是需要注意的是,任何在指令参数中的空白符是会保留的,如下:
 1
2
3 >RUN echo "\
hello\
world"执行结果如下:
 1
2
3 Step 2/4 : RUN echo " hello world"
Running in c672a42f820e
hello world对于空白符,这点和注释的解析是有区别的,前面说了对于任何以
#开头(除了解析器指令)都会被认为是注释。
2.2.2. 解析器指令
前面已经提到了以#开头的行除了是注释以外,就是解析器指令了,那什么是解析器指令呢?
解析器指令是可选的,会影响Dockerfile随后指令行的执行;解析器指令不会增加Image构建的layers;并且不会作为构建step显示;解析器指令的写法上一种特殊的注释,如下:
1  | # directive=value  | 
解析器指令一定要放在Dockerfile最开始,一旦Docker处理到任何注释,空白行,构建指令之后,Docker后面的指令处理不会再处理任何看上去是解析器指令的行,而是把他们当成注释;解析器指令同样大小写不敏感,但是习惯上用小写,且解析器指令后面留一行空白行;对于解析器指令不支持换行转义符;
如下由于不支持换行转义符,下面的语句会被当做是注释;
1  | # direc \  | 
同样的解析器指令会被认为无效:
1  | # directive=value1  | 
非放在最开始的解析器指令都会被认为是注释,如下,全部都是注释:
1  | # About my dockerfile  | 
目前Docker只支持两种解析器指令:syntax和escape指令;其他不支持的指令都会被认为是注释;
syntax指令
syntax的格式如下:
1  | # syntax=[remote image reference]  | 
例如:
1  | # syntax=docker/dockerfile:1  | 
2.2.3. 构建指令
A. FROM指令
前面说了,Dockerfile的第一条构建指令必须是FROM指令(除了上面提到的三种特例);FROM指令用来初始化一个新的构建阶段并指定构建本Image所需要依赖的父Image,后续所有的指令都是基于此base image进行的,直到结束构建或者一个新的FROM指令。
1  | 
  | 
--platfrom参数:用于指定依赖的特定平台的父镜像,因为有些镜像可能存在多个平台的版本;例如:linux/amd64,linux/arm64,windows/amd64;默认情况下,使用Docker daemon所使用的的平台来进行构建;image参数:就是所依赖的父镜像名字;对于只image是镜像名字的,回去Docker Hub中查找和拉取镜像,如果image是镜像的URL,那么会去该Registry搜索和拉取镜像;AS name参数:本构建的别名,可以在后面的构建指令中进行引用,例如COPY --from=<name>;tag和digest参数:用来指定所依赖的特定版本的父镜像,不填,默认使用名为latesttag的镜像版本;
在同一个Dockerfile构建文件中,可以出现多条FROM指令;每一条FROM指令都会清除之前所有指令设置的状态,开始一个新的stage。你可以选择将前面构建的产物拷贝到新的stage中,之前的构建stage全部丢弃,如下官方示例:
1  | # syntax=docker/dockerfile:1  | 
B. RUN指令
RUN构建指令会在新的layer中执行指定的命令,并将执行结果进行提交,用于后面step的构建;RUN指令有如下两种格式:
1  | # shell模式:执行shell命令, 默认 /bin/sh -c on Linux or cmd /S /C on Windows  | 
相对于shell模式,exec模式可以避免shell语法中字符串重整的问题;shell模式默认的shell解析器可以通过SHELL指令进行修改;
shell模式下的命令语法就是正常的shell语法,docker会拉起shell解析器解析后面的shell语句,例如\反斜线是转义符号,如下:
1  | RUN /bin/bash -c 'source $HOME/.bashrc; \  | 
等价于
1  | RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'  | 
同样可以通过exec模式来指定shell进行命令执行,如下:
1  | RUN ["/bin/bash", "-c", "echo hello"]  | 
需要注意的是:exec模式的后面的参数是一个JSON数组,所以里面的参数都必须用双引号包裹,且需要符合JSON字符串的语法,对于特殊字符需要进行转义;例如:
1  | RUN ["c:\windows\system32\tasklist.exe"]  | 
RUN指令执行的cache在一下次docker build仍然是可见的;这样在一下次构建的时候,可以快速使用cache,而不用重新进行耗时的构建,如下,每个step都是使用上一次构建的cache:
1  | Step 2/10 : MAINTAINER walkerdu  | 
RUN指令的cache在以下情况下会失效:
ADD和COPY指令后面的RUN构建指令的cache可能(判断文件发生变化)会失效;- 如果想强制进行重新构建,需要显示的在构建时带上no-cache参数,如:
docker build --no-cache; 
当某一步的cache被判断失效后,后面所有的step都会重新进行构建,所以设计Docker的时候,应该尽量:不常变动的,放在前面构建,常变动的,放在后面构建;
分层的RUN指令和生成的提交符合Docker的核心设计理念:提交成本很低,且可以从Image历史的任何位置进行容器的创建;这个代码的版本控制很相似;
C. CMD指令
CMD指令是用来指定容器运行时默认要执行的命令;CMD指令也可以只指定特定的参数,由ENTRYPOINT指令指定可执行文件;CMD指令的格式如下:
1  | # exec模式:建议使用的格式  | 
CMD指令在整个Dockerfile中只能存在一条有效的,如果有多条CMD指令,只有最后一条有效果;对于exec模式和shell模式,CMD指令都是来设置Image运行时要执行的命令;使用参数模式:CMD指令为ENTRYPOINT指令提供默认参数,CMD指令和ENTRYPOINT指令的参数都必须要是JSON数组格式;
对于exec模式和shell模式,的相关用法和RUN指令是一样的;需要注意的是:不要把RUN指令和CMD指令搞混了,RUN指令只是在构建Image的时候执行特定的命令,然后提交结果;而CMD指令不会在构建的时候执行,而是设置Image运行时期望的执行的指令;
如果执行docker run启动container的时候指定了特定的命令,那么会忽略CMD指令的设置;
D. LABEL指令
LABEL指令目的是为了给Image增加一些元数据。一个LABEL由一个键值对组成,格式如下:
1  | LABEL <key>=<value> <key>=<value> <key>=<value> ...  | 
对于含有空格的value,需要通过双引号或者反斜线进行转义;多标签可以放在同一行指令中;在Docker 1.10之前,多标签放在同一行可以减少Image的大小,不过现在不存在这个问题了;如下是简单的额示例:
1  | LABEL "com.example.vendor"="ACME Incorporated"  | 
标签会从父镜像继承(FROM 指令)其设置,如果有标签发生冲突,那么最新的设置会覆盖之前的设置;可以通过docker image inspect来查看镜像的标签;
1  | docker image inspect --format='' imageid  | 
E. MAINTAINER指令
MAINTAINER指令用来设置镜像构建的作者,已经不建议使用;LABEL指令可以更灵活的做这个事情,他可以添加任何你需要的元数据;如下:
1  | LABEL org.opencontainers.image.authors="[email protected]"  | 
F. EXPOSE指令
EXPOSE指令用于告知Docker,Container在运行时监听指定的端口;可以指定监听端口在TCP或UDP上,默认不指定为监听TCP端口;EXPOSE指令的格式如下:
1  | EXPOSE <port> [<port>/<protocol>...]  | 
注意:EXPOSE指令并不实际让Container暴露对应的端口,它的作用只是一个Image构建者给一个Image使用者的文档说明;旨在说明Image需要暴露的端口,而实际Container运行时实际的端口暴露,需要Container运行时通过指定的配置来使其对应的端口暴露;
1  | EXPOSE 80  | 
image inspect的结果如下:
1  | "ExposedPorts": {  | 
docker run -p/-P客户端命令可以使容器在运行的时候绑定指定的container端口到host的端口上,以保证服务对外提供访问能力;
G. ENV指令
ENV指令用于设置环境变量,环境变量的值设置后在随后的构建step中都是可见的,可以将环境变量用在后面的构建命令参数中,作为变量值来进行后面构建命令的构建使用;ENV指令格式如下,多个键值对可以在同一行;
1  | ENV <key>=<value> ...  | 
ENV指令还有一种不建议使用的语法,只是为了向后兼容,未来可能被移除,如下
 1 ENV key value这种语法省略了
=,不允许在同一行定了多个环境变量
如下示例,和命令解释类似,引号和转义符号可以用在包含空格的值中;
1  | ENV MY_NAME="John Doe"  | 
构建指令中对于环境变量的引用格式为:$variable_name或者${variable_name},熟悉shell语法的人会发现这个和shell语法是一致的;${variable_name}语法可以支持一些标准的bash修饰符,如下:
${variable:-word}:如果变量variable已经存在,那么返回其值,否则返回word(word可以是任何字符串,包括其他环境变量);${variable:+word}:如果变量variable已经存在,那么返回word字符串;否则返回空串;
如下是环境变量在后续构建指令中引用的示例:
1  | FROM busybox  | 
环境变量的引用范围不能在同一条构建指令中,如下:
1  | ENV abc=hello  | 
环境变量的支持在以下构建命令中进行引用:ADD,COPY,ENV,EXPOSE,FROM,LABEL,STOPSIGNAL,USER,VOLUME,WORKDIR,ONBUILD;测试发现RUN指令里面也可以进行环境变量的操作;详细参考environment replcaement手册;
通过ENV设置的环境变量会在Container运行的时候一直存在,而ARG变量在image构建结束后就失效了;可以通过docker inspect进行查看,也可以在Container运行的时候进行重新设定,如:docker run --env <key>=<value>;
H. ADD指令
ADD指令用于将本地的文件,目录或者是远端的URLs拷贝到构建的Image中的文件系统中;指令的格式如下:
1  | ADD [--chown=<user>:<group>] <src>... <dest>  | 
src...dest:拷贝源文件和目标目录;可以指定多个src资源,如果他们是本地的文件或目录,那么他们的相对路径是构建上下文的PATH;src资源可以包含通配符用来匹配多个资源,通配符匹配规则采用Go的filepath.Match;dest目标可以是一个绝对路径,或者是一个和WORKDIR指令关联的相对路径;如下是一些简单的使用示例:
1  | # 拷贝以hom开头的源文件  | 
--chown用来在拷贝文件到Image的文件系统中时设置对应文件新的用户名和用户组;默认不设置时,拷贝文件的UID和GID都是0,即root超级用户权限;该选项只支持构建Linux的Image;
如果拷贝的是本地文件或目录,拷贝时,并不会修改文件和目录的访问权限,如果拷贝的src是URL的话,目标文件的访问权限是600;
--chown选项支持设置用户名,用户组或者直接是UID,GID的组合的方式来设置文件权限;如果设置了用户名,但是没有设置用户组,或者设置了UID,但没有设置GID,此时用户组或者GID都会自动根据用户名/UID,设置为相同的值;这个设置过程Docker会通过Image的文件系统的/etc/passwd和/etc/group两个文件进行相关的翻译和转换;所以要设置的权限必须是Image已经存在的,否则会构建出错;如下相关示例:
1  | ADD --chown=55:mygroup files* /somedir/  | 
ADD指令在拷贝的时候遵循以下原则:
src路径必须是构建上下文中的,不能有类似ADD ../something /dest的构建,因为docker build会将构建上下文全部发送给Docker daemon进行构建,所有PATH之外的文件时找不到的,无法执行构建;- 如果
src是URL且dest没有以斜线结尾,那么一个文件会从URL进行下载并拷贝为dest - 如果
src是URL且dest以斜线结尾,那么一个文件会被拷贝到dest目录下,例如:ADD http://example.com/foobar /会创建dest/filename; - 如果
src是一个目录,则所有该目录下的文件都会进行拷贝,包括元数据;注意:只拷贝目录下的所有文件,不会拷贝目录本身; - 如果
src是一个可被识别的压缩文件,那么将会被解压作为一个目录处理(URL指向的压缩包不会执行此操作);类似执行tar -x的结果; - 如果有多个
src被指定,无论是直接指定多个,还是通过通配符匹配的结果,dest都必须是一个目录且以正斜线结尾; - 如果
dest没有以斜线结尾,那么其会被认为是一个文件,src会直接写入dest文件中; - 如果
dest不存在,构建时会自动创建所有不存在的目录; 
I. COPY指令
如果你看官方手册,会发现其内容基本和ADD指令基本一样,ADD指令相对于COPY指令多了两个功能,其他完全一样:
ADD指令允许src是URL;ADD指令可以识别src是压缩文件;
官方最佳实践建议首选采用COPY指令,因为COPY指令相对于ADD指令更加简单,仅支持文件的基本拷贝;如果需要上门两个特性可以使用ADD指令来提供高级功能;
J. ENTRYPOINT指令
ENTRYPOINT指令也是用于配置container运行时要执行的命令,ENTRYPOINT指令的有如下两种格式:
1  | # exec模式  | 
docker run <image>启动时传入的命令行参数都会被添加到ENTRYPOINT的exec模式的参数中,且CMD指令参数模式的所有参数会被全部覆盖;可以通过docker run --enterypoint来覆盖ENTRYPOINT指令;
如下示例,将docker run <image>输入的参数在Container中输出到文件中:
1  | FROM public.ecr.aws/lts/ubuntu  | 
以如下方式运行Image:
1  | docker build -t dockertest/test .  | 
可以看到输出的参数如下:
1  | docker exec -it 2fde1891bb9b cat /helloworld.txt  | 
在ENTRYPOINT的shell模式下,CMD指令的参数和docker run命令的参数都会被忽略的;使用ENTRYPOINT的shell模式有一个很大的缺点(CMD的Shell模式一样):作为/bin/sh -c的shell命令,不会传递信号给子命令;执行的程序不会以PID=1运行,且无法接收到Unix信号;例如:docker stop <container>无法将SIGTERM信号传递给运行的进程;
和CMD指令一样,对于多条ENTRYPOINT指令,只有最后一条有效;
对于CMD指令和ENTRYPOINT指令的功能有很多相似之处,他们都是用来定义Container运行时要执行的命令;对于它们两个的使用方式和协作建议如下:
- Dockerfile必须含有至少一条
CMD指令或者ENTRYPOINT指令; ENTRYPOINT指令应该作为可执行Container的指令;CMD指令应该作为提供默认参数的方式为ENTRYPOINT指令运行程序;CMD指令提供的参数可以在Container运行的时候被覆盖;
下表说明了CMD指令和ENTRYPOINT指令不同组合的使用情况:
| No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry(shell模式) | **ENTRYPOINT [“exec_entry”, “p1_entry”]**(exec模式) | |
|---|---|---|---|
| No CMD | 构建错误 | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry | 
| **CMD [“exec_cmd”, “p1_cmd”]**(exec模式) | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd | 
| **CMD [“p1_cmd”, “p2_cmd”]**(ENTRYPOINT参数模式) | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd | 
| CMD exec_cmd p1_cmd(shell模式) | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd | 
K. VOLUME指令
VOLUME指令用于在Container运行时在宿主机上持久化一些数据,当然也可以和其他Container共享数据;当含有VOLUME指令的Image运行时,Docker会在本地磁盘上创建一个匿名的volume,并将此volume挂载到Container中VOLUME指令声明的挂载点上;如果 Container中的挂载点上有数据,则会拷贝到本地匿名的volume中;
VOLUME指令的格式如下:
1  | # json格式  | 
如下示例:
1  | FROM ubuntu  | 
在容器运行后,Docker deamon会在本地磁盘上创建一个匿名的volume,并挂载到Container的/myvol目录,同时把/myvol内的文件拷贝到volume中;我们可以看到在 /var/lib/docker/volumes 目录下创建了一个匿名的volume,里面含有Container创建的greeting文件;
1  | docker volume ls  | 
这里需要注意的是:VOLUME指令默认在Container运行的时候创建的匿名Volume,虽然是持久化的,但是无法与其他Container共享;可以通过docker run -v volume_name:/dir来为VOLUME指令创建有名的volume,然后挂载到VOLUME指令设置的挂载点;如下:
1  | docker run -it -v test_volume:/myvol dockertest/test /bin/bash  | 
L. USER指令
USER指令用于指定用户名和用户组来运行后面的RUN,CMD,ENTRYPOINT构建指令;当然这里的用户名和用户组必须是已经存在;
如果用户名没有所属的主用户组,那么会以root用户组的身份执行命令
如下示例:
1  | FROM ubuntu  | 
M. WORKDIR指令
WORKDIR指令用于设置后面构建指令操作的工作目录,如果目录不存在,会自动创建;工作目录的设置对于RUN,CMD,ENTRYPOINT,COPY,ADD构建指令生效;指令格式如下:
1  | WORKDIR /path/to/workdir  | 
WORKDIR指令可以设置多次,如果参数是一个相对路径,那么其相对的路径是相对于最近一条WORKDIR指令所设置的路径,如下:
1  | FROM ubuntu  | 
执行结果如下,中间省略了很多输出;
1  | Step 2/7 : RUN pwd  | 
默认工作目录是根目录;如果不指定,默认会继承FROM xxx基础镜像设置的WORKDIR,所以为了避免不可控的工作目录,显示设置WORKDIR是一个好的习惯;
N. ARG指令
ARG指令用于申明一个变量可以在后面的构建过程中使用的,通过docker build --build-arg <var>=<value>命令在Image构建过程中设置ARG参数变量的值; --build-arg传入的变量必须已经在Dockfile中通过ARG 指令定义,否则会在构建过程中触发Warning;
ARG指令格式如下:
1  | ARG <name>[=<default value>]  | 
一个Dockerfile可以包含多个ARG指令,ARG指令定义的参数名可以包含默认值,如果docker build --build-arg为设置参数的值,构建会使用ARG的默认值;如下简单的示例:
1  | FROM ubuntu  | 
执行如下构建:
1  | docker build --build-arg var1=val1 -t dockertest/test .  | 
需要注意的是,不建议在构建的时候传入密钥,证书等参数信息,因为构建过程中传入的参数信息可以通过
docker history查看构建过程中的所有信息;
ARG变量定义从Dockerfile中定义它的行开始生效,而不是通过在命令行或其他地方使用参数而生效;在本构建Stage结束后,ARG变量生命周期会结束;而ENV变量会在Container运行时一直生效;需要注意的是:ARG支持使用的指令和ENV一样,只能在特定的指令后面使用,具体参考environment replcaement手册或ENV指令一节;
ARG指令和ENV指令都可以定义变量在RUN命令中使用,这里需要注意:ENV定义的环境变量会覆盖ARG定义的同名变量;如下示例:
1  | FROM ubuntu  | 
Docker提供一个预定义的ARG变量集合,可以直接使用,而不需要通过ARG指令进行定义:如下预定义变量集合:
HTTP_PROXYhttp_proxyHTTPS_PROXYhttps_proxyFTP_PROXYftp_proxyNO_PROXYno_proxy
如下构建示例:
1  | FROM ubuntu  | 
构建过程如下:
1  | docker build --build-arg http_proxy=a.b.c -t dockertest/test .  | 
2.3 Image构建之最佳实践
Docker官方给出了很多Docker在构建的时候一些比较好的Dockerfile设计规范和Image构建的最佳方案,如下几点:
关于Image构建的一些比较好的推荐方案,如下:
还有一些参考资料:
后面会逐渐展开学习和分享;
