Dockerfile的入坑路
0x00 前言在经历了无数次的手动搭建环境之后,尝试过无尽的绝望(稍微有些夸张了),最后决定把Dockerfile学起来,要弄的镜像实在太多了,创建好基础镜像之后使用Dockerfile可以减轻很多工作,所以特地整理了这份Dockerfile相关命令。0x01 docker builddocker build .使用当前目录下的Dockerfile文件创立新的镜像,但是RE...
0x00 前言
在经历了无数次的手动搭建环境之后,尝试过无尽的绝望(稍微有些夸张了),最后决定把Dockerfile学起来, 要弄的镜像实在太多了,创建好基础镜像之后使用Dockerfile可以减轻很多工作,所以特地整理了这份Dockerfile相关命令。
0x01 docker build
- docker build .
使用当前目录下的Dockerfile文件创立新的镜像,但是REPOSITORY
和TAG
都是默认的<none>
,所以这样做个人觉得不是很好的。
- docker build -f /path/Dockerfile .
可以使用-f
标志docker build
指向文件系统中任何位置的Dockerfile
。
- docker build -t REPOSITORY:TAG .
如果构建成功,该命令可以指定存储库和标记以保存新镜像。
要在构建后将映像标记为多个存储库,请在-t运行build命令时添加多个参数:
docker build -t REPOSITORY1:TAG1 -t REPOSITORY2:TAG2 .
0x02 Dockerfile的语法
创建Dockerfile的原则:
- 更快的构建速度
- 更小的Docker镜像大小
- 更少的Docker镜像层
- 充分利用镜像缓存
- 增加Dockerfile可读性
- 让Docker容器使用起来更简单
2.1 FROM
Dockerfile文件是以FROM
指令开始的,通过该指令从docker hub
上或者本地存储库中拉取镜像作为基本镜像,基础镜像选用alpine比较合适,这个镜像不到5M。
- FROM <image> [AS <name>]
- FROM <image>[:<tag>] [AS <name>]
2.2 RUN
RUN有两种形式:
- RUN (shell形式)
- RUN [“executable”, “param1”, “param2”](exec形式)
exec形式被解析为JSON数组,这意味着必须使用双引号(“)
,而不是单引号(')
该RUN指令将在当前图像之上的新层中执行任何命令并提交结果。生成的提交图像将用于下一步Dockerfile。
RUN在下一次构建期间,指令的缓存不会自动失效。类似指令的缓存RUN apt-get dist-upgrade -y
将在下一次构建期间重用。例如,RUN可以通过使用--no-cache
标志使指令的高速缓存无效docker build --no-cache .
注意:
- 将多个RUN指令合并为一个
- 基础镜像的标签不要用latest
- 每个RUN指令后删除多余文件
使用\
将多个RUN
指令合成一个,可以有效减少容器层数以及镜像大小,当我们更新了apt-get源,下载安装了一些软件包,它们都保存在/var/lib/apt/lists/
目录中。但是,运行应用时Docker镜像中并不需要这些文件。最好将它们删除,因为这会使Docker镜像变大。
FROM ubuntu:18.04
RUN apt-get update && \
apt-get install -y nginx && \
apt-get install -y nodejs && \
&& rm -rf /var/lib/apt/lists/*
2.3 CMD
该CMD指令有三种形式:
- CMD [“executable”,”param1”,”param2”](exec形式,这是首选形式)
- CMD [“param1”,”param2”](作为ENTRYPOINT的默认参数)
CMD command param1 param2(shell形式)
- 一个
Dockerfile
中只能有一条CMD指令。如果列出多个CMD,则只有最后一个CMD生效。 - 如果
docker run
指定了其他命令,CMD 指定的默认命令将被忽略
- 一个
使用 shell 格式的话,实际的命令会被包装为sh -c
的参数的形式进行执行:
CMD echo $HOME
在实际执行中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]
当我们在镜像中安装了nginx服务,并且希望这个服务随着容器的启动而一起启动时,如果我们写成shell的形式,sh作为主进程,当执行完启动命令之后,主进程退出,容器就失去了存在的意义,从而退出。命令如下(可理解为CMD [ "sh", "-c", "service nginx start"]
):
CMD service nginx start
应该直接执行nginx
可执行文件,并且要求以前台形式运行:
CMD ["nginx", "-g", "daemon off;"]
注意:不要混淆RUN和CMD。RUN实际上运行一个命令并提交结果; CMD在构建时不执行任何操作,但指定镜像的预期命令。
2.4 EXPOSE
EXPOSE <port> [<port>/<protocol>...]
EXPOSE
指令用于指定容器将要监听的端口,我们可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认为TCP。因此,应该为应用程序使用常见的端口。例如,提供 Apache web
服务的镜像应该使用EXPOSE 80
,而提供 MongoDB
服务的镜像使用 EXPOSE 27017
。
对于外部访问,我们可以在执行docker run
时使用-p
标志来指示如何将指定的端口映射到所选择的端口。
默认情况下,EXPOSE假定为TCP。我们可以指定UDP:
EXPOSE 80/udp
要在TCP和UDP上公开,可以包含两行:
EXPOSE 80/tcp
EXPOSE 80/udp
无论EXPOSE设置如何,可以使用-p标志在运行时覆盖它们。例如
docker run -p 80:80/tcp -p 80:80/udp ...
2.5 LABEL
LABEL <key>=<value> <key>=<value> <key>=<value> ...
该LABEL指令将元数据添加到镜像。LABEL是键值对。要在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"
基本或父镜像中包含的标签(FROM线中的镜像)由镜像继承。如果标签已存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。
要查看镜像的标签,可以使用docker inspect
命令。
"Labels": {
"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"
},
2.6 MAINTAINER
MAINTAINER <name>
该MAINTAINER
指令设置生成的镜像的Author字段。而LABEL指令是一个更加灵活的版本,我们应该使用它,因为它可以设置我们需要的任何元数据,并且可以轻松查看,例如使用docker inspect
。要设置与MAINTAINER
我们可以使用的字段对应的标签 :
LABEL maintainer="SvenDowideit@home.org.au"
2.7 ENV
ENV <key> <value>
ENV <key>=<value> ...
第一种形式,
ENV <key> <value>
将一个变量设置为一个值。第一个空格后的整个字符串将被视为<value>
-包括空格字符。该值将针对其他环境变量进行解释,因此如果未对其进行转义,则将删除引号字符。第二种形式
ENV <key>=<value> ...
允许一次设置多个变量。请注意,第二种形式在语法中使用等号(=),而第一种形式则不然。与命令行解析一样,引号和反斜杠可用于在值内包含空格。
第一种形式:
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
第二种形式:
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
我们可以使用ENV
来为容器中安装的程序更新PATH
环境变量。例如使用ENV PATH /usr/local/nginx/bin:$PATH
来确保CMD ["nginx"]
能正确运行。
ENV
指令也可用于为你想要容器化的服务提供必要的环境变量。
ENV
也能用于设置常见的版本号,示例如下:
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
这类似于程序中的常量,我们只需改变ENV
指令就能改变容器中的软件版本。
2.8 COPY 和 ADD
COPY有两种形式:
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (包含空格的路径需要这种形式)
–chown功能仅在用于构建Linux容器的Dockerfiles上受支持
- 该
COPY
指令从中复制新文件或目录<src>
,并将它们添加到路径中容器的文件系统中<dest>
。 <src>
可以指定多个资源。- 每个
<src>
都可以包含通配符。
COPY hom* /mydir/ # adds all files starting with "hom"
COPY hom?.txt /mydir/ # ? is replaced with any single character, e.g., "home.txt"
COPY test relativeDir/ # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/ # adds "test" to /absoluteDir/
COPY 遵守以下规则:
该路径必须是内部语境的构建; 不能COPY ../something /something,因为第一步
docker build
是将目录(和子目录)发送到docker守护进程。如果是目录,则复制目录的全部内容,包括文件系统元数据。
注意:不复制目录本身,只复制其内容。
如果是任何其他类型的文件,则将其与元数据一起单独复制。在这种情况下,如果以尾部斜杠结尾/,则将其视为目录,并将写入内容/base()。
如果直接或由于使用通配符指定了多个资源,则必须是目录,并且必须以斜杠结尾
/
。如果不以尾部斜杠结束,则将其视为常规文件,并将写入其中的内容。
如果不存在,则会在其路径中创建所有缺少的目录。
示例Dockerfile:
FROM node:7-alpine
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
ENTRYPOINT ["./entrypoint.sh"]
CMD ["start"]
COPY指令非常简单,仅用于将文件拷贝到镜像中。ADD相对来讲复杂一些,可以用于下载远程文件以及解压压缩包,但是语法上基本差不多。
2.9 ENTRYPOINT
ENTRYPOINT有两种形式:
- ENTRYPOINT [“executable”, “param1”, “param2”] (exec形式,首选)
Dockerfile
显示使用ENTRYPOINT
在前台运行Apache
即作为PID 1):
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
辅助脚本被拷贝到容器,并在容器启动时通过ENTRYPOINT
执行:
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
- ENTRYPOINT command param1 param2 (shell形式)
我们可以为ENTRYPOINT
指定一个纯字符串,它将在其中执行/bin/sh -c
,这种形式将使用shell
处理来替换shell
环境变量,并将忽略任何CMD
或docker run
命令行参数。为了保证docker stop
,ENTRYPOINT
正确地发出任何长时间运行的可执行文件:
FROM ubuntu
ENTRYPOINT exec top -b
2.10 CMD和ENTRYPOINT
Dockerfile应至少指定一个CMD或ENTRYPOINT命令。
ENTRYPOINT 应该在将容器用作可执行文件时定义。
CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行
ad-hoc
命令的方法。
2.11 VOLUME
VOLUME
指令用于暴露任何数据库存储文件,配置文件,或容器创建的文件和目录。强烈建议使用VOLUME
来管理镜像中的可变部分和用户可以改变的部分。
2.12 WORKDIR
WORKDIR /path/to/workdir
该WORKDIR指令集的工作目录对任何RUN,CMD, ENTRYPOINT,COPY和ADD
有效。如果WORKDIR
不存在,即使它未在任何后续Dockerfile
指令中使用,也将创建它。
该WORKDIR指令可以在Dockerfile多次使用。如果提供了相对路径,则它将相对于前一条WORKDIR指令的路径 。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
最终pwd
命令的输出Dockerfile
将是/a/b/c
。
该WORKDIR
指令可以解析先前使用的环境变量ENV
。我们只能使用显式设置的环境变量Dockerfile
。例如:
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
最终pwd
命令的输出Dockerfile
将是/path/$DIRNAME
。
Dockerfile:
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
此Dockerfile
会创建新的挂载点/myvol
并将greeting
文件复制到新创建的VOLUME
中。
0x03 .dockerignore文件
在docker CLI
将上下文发送到docker
守护进程之前,它会查找.dockerignore
在上下文的根目录中指定的文件。如果此文件存在,CLI
将修改上下文以排除与其中的模式匹配的文件和目录。这有助于避免不必要地将大型或敏感文件和目录发送到守护程序,并可能使用ADD
或将它们添加到镜像中COPY
。
示例.dockerignore文件:
# comment
*/temp*
*/*/temp*
temp?
以!(感叹号)开头的行可用于对排除项进行例外处理。以下是.dockerignore
使用此机制的示例文件:
*.md
!README.md
参考文档
更多推荐
所有评论(0)