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;FROM
构建指令的格式如下:
1 | FROM [--platform=<platform>] <image> [AS <name>] |
--platfrom
参数:用于指定依赖的特定平台的父镜像,因为有些镜像可能存在多个平台的版本;例如:linux/amd64
,linux/arm64
,windows/amd64
;默认情况下,使用Docker daemon所使用的的平台来进行构建;image
参数:就是所依赖的父镜像名字;对于只image是镜像名字的,回去Docker Hub中查找和拉取镜像,如果image是镜像的URL,那么会去该Registry搜索和拉取镜像;AS name
参数:本构建的别名,可以在后面的构建指令中进行引用,例如COPY --from=<name>
;tag
和digest
参数:用来指定所依赖的特定版本的父镜像,不填,默认使用名为latest
tag的镜像版本;
在同一个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="SvenDowideit@home.org.au" |
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_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_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构建的一些比较好的推荐方案,如下:
还有一些参考资料:
后面会逐渐展开学习和分享;