使用Dockerfile定制镜像
镜像的定制实际上就是定制每一层所添加的配置、文件。我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,这个脚本就叫做Dockerfile。
Dockerfile是一个文本文件,其内包含了一条条指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
创建Dockerfile
首先创建一个目录,然后在该目录里面创建Dockerfile文件。例如:
$ mkdir goweb $ cd goweb $ touch Dockerfile
保存
Dockerfile文件的目录,称之为构建环境,也叫构建上下文。Docker会在构建镜像时将构建上下文和该上下文中的文件和目录上传到Docker守护进程。这样Docker守护进程就能直接访问你想在镜像中存储的任何代码、文件或者其他数据。
以下面的文档为例来介绍如何使用:
FROM alpine MAINTAINER benben <benben@csdn.com> LABEL maintainer="benben <benben@csdn.com>" # 原utc时间,修改成cst(中国标准东八区时间) ENV TZ Asia/Shanghai RUN apk --update add tzdata ca-certificates && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \ echo ${TZ} > /etc/timezone WORKDIR /opt/benben-project COPY benbenProject benbenProject RUN chmod 755 ./benbenProject COPY conf/ ./conf RUN mkdir logs EXPOSE 8088 CMD ./benbenProject
FROM指定基础镜像
定制镜像,就是以一个镜像为基础,在其上进行定制。一个Dockerfile中
FROM是必不可少的指令,并且必须是第一条指令。用法如下:
FROM [AS ]
FROM [:] [AS ]
FROM [@] [AS ]
基础镜像可以是任何有效的镜像,通过从公共仓库中拉取镜像非常容易。本文的基础镜像是一个面向安全的轻型Linux发行版。
ARG
命令是Dockerfile中唯一可以在FROM
之前的指令,可以参阅随后的ARG和FROM如何交互。FROM
可以在单个Dockerfile中多次出现以创建多个映像,或者使用一个构建层作为另一个构建层的依赖。只需要在每个新的FROM
指令之前记下最后一个commit镜像的id,因为每个FROM
指令会清除事先所有指令创建的状态。tag
和digest
值是可选的,如果省略,则构建镜像时,会默认采用latest
标签。如果找不到,则构建器会返回错误。
除了选择现有镜像为基础镜像外,Docker还存在一个特殊的镜像,
scratch。这个镜像是虚拟的概念,实际并不存在,它表示一个空白的镜像。如果你以
scratch为基础镜像的话,意味着你不以任何镜像为基础。接下来的指令将作为镜像第一层开始。
ARG和FROM如何交互
FROM指令支持在第一个
FROM之前出现任何
ARG指令声明的变量。例如:
ARG CODE_VERSION=latest FROM base:${CODE_VERSION} CMD /code/run-app FROM extras:${CODE_VERSION} CMD /code/run-extras
在
FROM指令之前的
ARG指令位于构建阶段之外,因此在
FROM命令之后,是不能使用那些环境变量的。上面指令的第二行可以使用变量
CODE_VERSION,,但第五行就不能使用。因此要想使用在第一个
FROM之前声明的
ARG的默认值,需要使用在构建阶段的
ARG。
ARG VERSION=latest FROM busybox:$VERSION ARG VERSION RUN echo $VERSION > image_version
ARG构建参数
ARG指令的效果和
ENV的效果一样,都是设置环境变量。不同的是,
ARG设置的构建环境的环境变量,在将来容器运行时是不会存在的。但是不要因此保存密码之类的信息,因为通过
docker history命令还是可以看到所有值的。
用法
ARG <name>[=<default value>],该命令定义参数名称以及其默认值。默认值可以在构建命令
docker build中用
--build-arg <参数名>=<值>来覆盖。如果事先用
ARG设置了默认值,但却在构建时没有覆盖,那么构建就会使用默认值。
注意:
ARG变量在Dockerfile中定义的那行开始生效,而不是从命令行或其他地方使用处才开始。例如:
FROM busybox USER ${user:-some_user} ARG user USER $user
用户在构建时在命令行输入
$ docker build --build-arg user=what_user,那么值是如何传递的呢?
首先第二行的
USER值为
some_user,接着第三行定义了用户变量,第四行的
USER在定义
user时,其值通过命令行传递,得到
what_user。在
FROM指令支持在第一个
FROM之前出现任何
ARG指令声明的变量,
ARG指令定义之前,对变量的任何使用都会导致空字符串。
你可以使用
ARG或者
ENV指令来指定
RUN指令可用的变量,使用
ENV指令定义的环境变量始终会覆盖
ARG指令定义的同名变量。例如:
FROM ubuntu ARG CONT_IMG_VER ENV CONT_IMG_VER v1.0.0 RUN echo $CONT_IMG_VER
接着在命令行输入
$ docker build --build-arg CONT_IMG_VER=v2.0.1。在这种情况下,
RUN指令将会使用
v1.0.0而不是通过
ARG设置的
v2.0.1。
MAINTAINER(废弃)
MAINTAINER命令用来指定该镜像的作者,用法
MAINTAINER <name>。
LABEL命令是一个更灵活的版本,同样可以指定镜像的作者,推荐使用这个,官方已弃用这个命令。因为这个命令可以设置你需要的任何元数据,而且可以轻松查看。如果想通过
LABEL命令来设置和
MAINTAINER字段对应的标签,你可以这样使用:
LABEL maintainer="benben@csdn.com。通过命令
$ docker inspect [OPTIONS] NAME|ID [NAME|ID...]来查看其他标签,例如
$ docker inspect b92f7d0bb745。
LABEL
LABEL命令会添加元数据到镜像,用法:
LABEL <key>=<value> <key>=<value> <key>=<value> ...。如果要在
LABEL值中包含空格,需要使用引号和反斜杠,就像在命令行解析中一样。实例如下:
LABEL "com.example.vendor"="ACME Incorporated" LABEL com.example.label-with-value="foo" LABEL version="1.0" LABEL description="This text illustrates \ that label-values can span multiple lines."
镜像可以有多个标签,在
Docker 1.10之前,你可以在一行中指定多个标签。这减小了最终
镜像的大小,但现在不再是这种情况了。你仍然可以选择在单个指令中指定多个标签,方法有以下两种:
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \ multi.label2="value2" \ other="value3"
基础镜像或父镜像中的标签会被后添加的镜像继承,如果标签已存在但具有不同的值,那么后添加的值会覆盖原来设定的值。
"Labels":{ "maintainer": "benben <benben@csdn.com>" "com.example.vendor": "ACME Incorporated", "com.example.label-with-value": "foo", "version": "1.0", "description": "This text illustrates that label-values can span multiple lines.", "multi.label1": "value1", "multi.label2": "value2", "other": "value3" }
注释
接着那一行为注释,Dockerfile中以
#开头的行都会被认为是注释。
ENV设置环境变量
ENV指令将环境变量key的值设置为value,用法
ENV <key> <value>或者
ENV <key>=<value>。设置的环境变量将在后续的所有镜像中,同时也可以被替换。如果值中存在空格,则需要使用反斜杠。例如:
ENV myName="John Doe" myDog=Rex\ The\ Dog \ myCat=fluffy
上面的命令和下面的。
ENV myName John Doe ENV myDog Rex The Dog ENV myCat fluffy
当从生成的镜像运行容器时,使用
ENV设置的环境变量将保持不变。同样,可以使用
docker inspect命令来查看环境变量的值。
"Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TZ=Asia/Shanghai" ],
注意:永久环境变量也可能会导致副作用。例如,设置
ENV DEBIAN_FRONTEND noninteractive可能会使
apt-get获取
Debian基础镜像的用户迷惑。要为单个命令设置值,使用
RUN <key>=<value> <command>。
RUN
RUN命令会在当前镜像中运行指定的命令。具有两种形式:
RUN <command>
,(shell形式,RUN
指令会在shell里使用/bin/sh -c
来执行);RUN ["executable","param1","param2"]
,(exec形式,不支持shell的平台上运行或者不希望在shell中运行,也可以使用此格式)。
注意:与shell形式不同,exec格式不会自动调用shell命令,也就是说一般的shell命令是不能正常执行的。例如
RUN ["echo", "$HOME"]将不会对变量进行替换,如果想实现这样的结果,就需要这样
RUN ["sh","-c","echo $HOME"]。
注意:使用exec格式时,如果有路径,需要使用反斜杠来转义。不然不能正确解析,例如
RUN ["c:\windows\system32\tasklist.exe"]应该写成
RUN ["c:\\windows\\system32\\tasklist.exe"]。
示例Dockerfile文件中的指令含义是:
- 第一句是更新
tzdata
软件包和ca-certificates
; - 第二句是为文件创建连接,这里的连接时符号连接(符号链接是一个新文件,而硬链接并没有建立新文件,当用
ls -l
命令列出文件时,可以看到符号链接名后有一个箭头指向源文件或目录。例如lrwxrwxrwx. 1 root root 35 Apr 20 15:45 localtime -> ../usr/share/zoneinfo/Asia/Shanghai
,最前面的l
表示软连接); - 第三句打印环境变量到
timezone
文件。
由于Dockerfile中每一个指令都会建立一层,
RUN也不例外。这里我用&&将三条命令串联起来,是为了减少镜像层的创建。Union FS最大层数限制是不超过127层,太多的层会使镜像非常臃肿,不仅增加了构建部署的时间,也容易出错。
同时Dockerfile支持shell类的行尾添加\的命令换行方式,以及行首#进行注释的格式。
示例Dockerfile文件中的
RUN chmod 755 ./benbenProject命令用来变更
benbenProject文件的权限,755表示:文件所有者可读可写可执行,与文件所有者同属一个用户组的其他用户可读可执行,其他用户组可读可执行。
示例Dockerfile文件中的
RUN mkdir logs命令用来创建一个logs目录。
WORKDIR指定工作目录
用法
WORKDIR <工作目录路径>,该命令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如果该目录不存在,
WORKDIR会帮你建立目录。
WORKDIR指令可以在Dockerfile中多次使用,如果提供了相对路径,则它将相对于先前
WORKDIR指令的路径。
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
pwd命令的输出结果将会是
/a/b/c。
WORKDIR可以解析通过
ENV设置的环境变量,例如下面结果将是
/path/$DIRNAME。
ENV DIRPATH /path WORKDIR $DIRPATH/$DIRNAME RUN pwd
示例Dockerfile文件中定义工作目录为
/opt/benben-project。
COPY复制文件
COPY指令将从构建上下文目录中<源路径>的文件/目录复制到新的一层镜像内的<目标路径>位置。命令具有两种形式:
COPY [--chown=<user>:<group>] <src>...<dest>
COPY [--chown=<user>:<group>] ["<src>",..."<dest>"]
,这种形式允许路径中有空格
如果有多个源路径,那么文件和目录的路径将被解释为相对于构建上下文的源。源路径在设置时只要满足Go的路径匹配规则就可以,例如:
COPY hom* /mydir/ #会添加所有以hom开头的文件 COPY hom?.txt /mydir/ #匹配任何以hom开头,以.txt结尾的文件
源路径如果是个目录,那么目录的所有内容都会被copy,包括文件系统的元数据。目录本身没有被复制,只是复制其内容。
目标路径可以是绝对路径或者相对于
WORKDIR的路径,例如:
COPY test relativeDir/ #会添加test文件到`WORKDIR`/relativeDir/目录 COPY test /absoluteDir/ #会田间test文件到/absoulteDir/目录
示例Dockerfile文件中的
COPY benbenProject benbenProject命令将会拷贝上下文的benbenProject文件到
/opt/benben-project/benbenProject。
EXPOSE声明端口
指令声明容器运行时提供的服务端口,这只是一个声明,在运行时并不会因为这个声明,应用就会开启这个端口的服务(处于安全的原因)。Docker并不会自动打开该端口,而是需要你在使用
docker run -P运行容器时来指定需要打开哪些端口。
命令格式
EXPOSE <port> [<port>/<protocol>...],可以指定端口的协议是TCP还是UDP,如果未指定,则默认为TCP。例如:
EXPOSE 8088/tcp EXPOSE 8088/udp
如果同时暴露TCP和UDP,那么使用
docker run -P,端口将针对TCP公开一次,针对UDP公开一次。尽管通过
EXPOSE可以设置,但是你也可以在运行时重写它们。例如:
docker run -p 8088:8088/tcp -p 8088:8088/udp ...
注意:要将
EXPOSE和在运行时使用
docker run -p <宿主端口>:<容器端口>区分开来,
-p是映射宿主端口和容器端口。换句话说,就是将容器的对应端口服务公开给外界访问,而
EXPOSE仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
CMD容器启动命令
容器就是进程,既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。
CMD指令就是用于指定默认的容器主进程的启动命令的。
命令格式有三种形式:
CMD ["executable","param1","param2"]
,(exec格式,官方给推荐这种形式)CMD ["param1","param2"]
,(作为ENTRYPOINT
的默认参数)CMD command param1 param2
,(shell格式)
在一个Dockerfile中只能有一个
CMD指令,如果你设置了多条
CMD指令,那么实际起作用的只是最后一条。
CMD的主要目的是为执行容器提供默认值,这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,你必须指定
ENTRYPOINT指令。如果
CMD用于为
ENTRYPOINT指令提供默认参数,则应使用json数组格式指定
CMD和
ENTRYPOINT指令。
注意:同样如前面所述,exec格式不能激活命令行,因此你想运行常用的shell命令,需要提前运行shell命令工具。例如:
CMD ["sh","-c","echo $HOME"]。这个shell是执行变量扩展的,而不是docker。
如果你希望容器每次都运行相同的可执行文件,那么应该考虑将
ENTRYPOINT与
CMD结合使用。
不要讲
RUN与CMD混淆,RUN实际上是运行一个命令并提交结果,CMD则在构建时不进行任何操作,它只是指定了镜像的预期命令。
整体执行讲解
Dockerfile中的指令会按顺序从上到下执行,所以应该根据需要合理安排指令的顺序。每条指令都会创建一个新的镜像层并对镜像进行提交。Docker大体上按照如下流程执行Dockerfile中的指令。
- Docker从基础镜像运行一个容器
- 执行一条指令,对容器做出修改
- 执行类似
docker commit
的操作,提交一个新的镜像层 - Docker再基于刚提交的镜像运行一个新容器
- 执行Dockerfile中的下一条指令,直到所有指令都执行完毕
参考文章:
- Docker 实践 05 使用Dockerfile定制镜像
- 使用Dockerfile定制镜像
- 使用Dockerfile定制LNMP环境镜像
- 【系列3】使用Dockerfile创建yum安装nginx服务的Centos Docker镜像
- 使用dockerfile构建nginx镜像的方法示例
- 如何使用Dockerfile构建镜像
- Docker基础-使用Dockerfile创建镜像
- 如何使用Dockerfile构建Docker镜像
- 使用Dockerfile构建镜像
- Docker学习笔记(3)-- 如何使用Dockerfile构建镜像
- 【系列2】使用Dockerfile创建带Apache服务的Centos Docker镜像
- 使用dockerfile来pull镜像并启动
- Docker使用Dockerfile创建支持ssh服务自启动的容器镜像
- 【系列8】使用Dockerfile创建带MongoDB的Centos Docker镜像
- docker学习笔记4.1-使用Dockerfile文件构建镜像
- Docker学习笔记(3)-- 如何使用Dockerfile构建镜像
- 使用Dockerfile创建支持ssh服务自启动的容器镜像
- 使用Dockerfile创建带Apache服务的Centos Docker镜像
- 使用Dockerfile构建镜像
- 使用 Dockerfile 构建镜像