0x00 前言

在经历了无数次的手动搭建环境之后,尝试过无尽的绝望(稍微有些夸张了),最后决定把Dockerfile学起来, 要弄的镜像实在太多了,创建好基础镜像之后使用Dockerfile可以减轻很多工作,所以特地整理了这份Dockerfile相关命令。

0x01 docker build

  • docker build .

使用当前目录下的Dockerfile文件创立新的镜像,但是REPOSITORYTAG都是默认的<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环境变量,并将忽略任何CMDdocker 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

参考文档

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐