DockerFile

官网文档:https://docs.docker.com/engine/reference/builder/

1、基本说明

Dockerfile 是用来构建 Docker 镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。

image-20220118170715196

在一个文件夹中,如果有一个名字为 Dockfile 的文件,其内容满足语法要求,在这个文件夹路径下执行如下命令,就可以按照描述构建一个镜像。

1
2
3
# name是镜像的名称,tag是镜像的版本或者是标签号,不写就是lastest。
# 注意后面有一个空格和点。
docker build --tag name:tag .

dockerfile 示例

1
2
3
4
5
6
7
FROM node:8.4

COPY . /app
WORKDIR /app
RUN npm install --registry=https://registry.npm.taobao.org
EXPOSE 3000
CMD node demos/01.js
  • FROM node:8.4:该 image 文件继承官方的 node image,冒号表示标签,这里标签是8.4,即8.4版本的 node。
  • COPY . /app:将当前目录下的所有文件(除了.dockerignore排除的路径),都拷贝进入 image 文件的/app目录。
  • WORKDIR /app:指定接下来的工作路径为/app。
  • RUN npm install:在/app目录下,运行npm install命令安装依赖。注意,安装后所有的依赖,都将打包进入 image 文件。
  • EXPOSE 3000:将容器 3000 端口暴露出来, 允许外部连接这个端口。
  • CMD node demos/01.js:它表示容器启动后自动执行node demos/01.js

2、Dockfile 语法

Dockerfile的基本指令有很多个,常用的有:FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD。下面对这些指令的用法一一说明。

所有的命令都用的是双引号,不是单引号,如果命令没有空格,不加引号也可以。

2.0、Dockerfile 基础

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数。
  2. 指令按照从上到下,顺序执行。
  3. # 表示注释。
  4. 每条指令都会创建一个新的镜像层并对镜像进行提交。

2.1、FROM

  • 用法:

    1
    2
    3
    4
    # tag 或 digest 是可选的,如果不使用这两个值时,会使用 latest 版本的基础镜像
    FROM <image>
    FROM <image>:<tag>
    FROM <image>:<digest>
  • 说明

    • FROM 拉取镜像的地址。其指定一个构建镜像的基础源镜像,如果本地没有就会从公共库中拉取,没有指定镜像的标签会使用默认的latest标签。
    • FROM 必须 是 Dockerfile 中第一条非注释命令
    • 可以出现多次,如果需要在一个 Dockerfile 中构建多个镜像。

2.2、MAINTAINER

  • 用法:MAINTAINER <name> <email>

  • 描述:描述镜像的创建者,名称和邮箱

2.3、RUN

  • 用法:有两种格式

    1
    2
    3
    4
    # shell 格式
    RUN <command>
    # exec 格式
    RUN ["executable", "param1", "param2"]
  • 说明

    • RUN 命令是一个常用的命令,执行完成之后会成为一个中间镜像,这里也是指镜像的分层构建。
    • 一句RUN就是一层,也相当于一个版本。这就是缓存的原理。我们知道docker是镜像层是只读的,所以你如果第一句安装了软件,用完在后面一句删除是不可能的。所以这种情况要在一句RUN命令中完成,可以通过&符号连接多个RUN语句。
    • RUN后面的必须是双引号不能是单引号(没引号貌似也不要紧),command是不会调用shell的,所以也不会继承相应变量,要查看输入RUN “sh” “-c” “echo” “$HOME”,而不是RUN “echo” “$HOME”。
    • RUN 指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定 --no-cache 参数,如:docker build --no-cache

2.4、CMD

  • 用法:CMD 有以下三种格式:

    1
    2
    3
    CMD ["executable","param1","param2"]
    CMD ["param1","param2"]
    CMD command param1 param2
  • 说明

    • CMD 指定在启动容器的时候第一个要运行的命令。
    • CMD 在 Dockerfile 可以有多个,只有最后一个会有效。如果执行docker run的时候提供了命令项,就会覆盖掉这个命令。没提供就会使用构建时的命令。
  • 注意:与 RUN 指令的区别:RUN 在 build 的时候执行,并生成一个新的镜像,CMD 在容器 run 的时候执行,在构建时不进行任何操作。

2.5、ENTRYPOINT

  • 用法:

    1
    2
    3
    ENTRYPOINT ["executable", "param1", "param2"]
    ENTRYPOINT command param1 param2
    ENTRYPOINT "command" "param1" "param2"
  • 说明

    • ENTRYPOINT 也是用来指定一个容器启动时要运行的命令

    • ENTRYPOINT 与 CMD 非常类似,不同的是通过docker run执行的命令不会覆盖 ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给 ENTRYPOINT。

      • 如,执行 docker run <image> -d abc 时,-d abc将被传递给入口点。
      • ENTRYPOINT可以和CMD一起用,一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参。
    • 可以通过 docker run --entrypoint 重写 ENTRYPOINT 入口点。如docker run --entrypoint=/bin/bash you_image

    • Dockerfile 中可以有多个 ENTRYPOINT 命令,仅最后一个生效。

2.6、ENV

  • 用法:格式有两种:

    1
    2
    ENV <key> <value>
    ENV <key1>=<value1> <key2>=<value2>...
  • 说明:

    • 设置容器的环境变量,无论是后面的其它指令如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量,容器运行的时候这个变量也会保留。

    • 定义环境变量的同时,可以引用已经定义的环境变量。在ENV指令中,可以直接引用如下环境变量:

      1. HOME,用户主目录
      2. HOSTNAME,默认容器的主机名
      3. PATH,
      4. TERM,默认xterm
  • 示例

    1
    2
    3
    4
    5
    6
    ENV PATH /usr/local/bin:$PATH
    ENV name1=ping name2=on_ip

    # 下面这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的
    ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

2.7、ADD

  • 用法:

    1
    2
    ADD <源路径>... <目标路径>
    ADD ["<源路径>",... "<目标路径>"]
  • 说明:

    • 更高级的复制文件。
    • 复制本机文件或目录或远程文件,添加到指定的容器目录,支持GO的正则模糊匹配。路径是绝对路径,不存在会自动创建。如果源是一个目录,只会复制目录下的内容,目录本身不会复制。
    • ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。比如<
      • 源路径>可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到<目标路径>去。
      • ADD命令会将复制的压缩文件夹自动解压,这也是与COPY命令最大的不同。
  • 注意

    • 如果 docker 发现文件内容被改变,则接下来的指令都不会再使用缓存。关于复制文件时需要处理的/,基本跟正常的 copy 一致

2.8、COPY

  • 用法

    1
    2
    COPY <源路径>... <目标路径>
    COPY ["<源路径1>",... "<目标路径>"]
  • 说明

    • COPY除了不能自动解压,也不能复制网络文件。其它功能和ADD相同。
    • 此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。

2.9、EXPOSE

  • 用法:EXPOSE <port/协议> [<port/协议>...]

  • 示例:

    1
    2
    EXPOSE 8080
    EXPOSE 8090/udp
  • 说明:

    • EXPOSE 暴露的端口更像是 显式地声明 该容器提供的服务需要用到的端口,一定程度上提供了操作的便利,也提高了 Dockerfile 的可读性和可维护性。
    • 而且 EXPOSE 并不会直接将端口自动和宿主机某个端口建立映射关系。如果 EXPOSE 暴露的端口确定要和某个宿主机端口建立映射关系,还是要用到 docker run -p -P 参数。
    • 如果 docker run 指定了自动映射 -P,那么会将所有 EXPOSE 暴露的端口随机映射到宿主机的高阶端口。
    • 如果 docker run 指定了 --net=host 宿主机网络模式,容器中 EXPOSE 指令暴露的端口会直接使用宿主机对应的端口,不存在映射关系。

2.10、VOLUME

  • 用法:VOLUME ["path"]

  • 说明

    • 在主机上创建一个挂载,挂载到容器的指定路径。

    • docker run -v命令也能完成这个操作,而且更强大。这个命令不能指定主机的需要挂载到容器的文件夹路径。但docker run -v可以,而且其还可以挂载数据容器。

    • 一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:

      • 卷可以容器间共享和重用
      • 容器并不一定要和其它容器共享卷
      • 修改卷后会立即生效
      • 对卷的修改不会对镜像产生影响
      • 卷会一直存在,直到没有任何容器在使用它
    • VOLUME 让我们可以将源代码、数据或其它内容添加到镜像中,而又不并提交到镜像中,并使我们可以多个容器间共享这些内容。

2.11、USER

  • 用法:USER daemon

  • 说明

    • 指定运行容器时的用户名或UID,后续的RUN、CMD、ENTRYPOINT也会使用指定的用户运行命令。

    • 使用USER指定用户时,可以使用用户名、UID 或 GID,或是两者的组合。以下都是合法的指定试:

      1
      2
      3
      4
      5
      6
      USER user
      USER user:group
      USER uid
      USER uid:gid
      USER user:gid
      USER uid:group
    • 镜像构建完成后,通过 docker run 运行容器时,可以通过 -u 参数来覆盖所指定的用户。

2.12、WORKDIR

  • 用法:WORKDIR /path/to/workdir

  • 说明

    • 通过WORKDIR设置工作目录后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都会在该目录下执行。
    • 可以使用多个WORKDIR指令,后续参数如果是相对路径,则会基于之前的命令指定的路径。如:
      1
      2
      WORKDIR /home
      WORKDIR test
      最终的路径就是/home/test
    • path路径也可以是环境变量,比如有环境变量HOME=/homeWORKDIR $HOME/test也就是/home/test

2.13、ONBUILD

  • 用法:ONBUILD [INSTRUCTION]

  • 说明

    • 配置当前所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。意思就是,这个镜像创建后,如果其它镜像以这个镜像为基础,会先执行这个镜像的ONBUILD命令。
    • 通常用在当镜像被使用时,可能需要做一些预处理的工作。

2.14、LABEL

  • 语法

    1
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
  • 说明

    • LABEL用于为镜像添加元数据,元数以键值对的形式指定
    • 使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。
  • 示例:通过LABEL指定一些元数据:

    1
    LABEL version="1.0" description="这是一个Web服务器" by="IT笔录"

    指定后可以通过 docker inspect 查看:

    1
    2
    3
    4
    5
    6
    docker inspect itbilu/test
    "Labels": {
    "version": "1.0",
    "description": "这是一个Web服务器",
    "by": "IT笔录"
    },

2.15、ARG

  • 语法 ARG <name>[=<default value>]

  • 说明:ARG 用于指定传递给构建运行时的变量:

  • 示例:
    通过 ARG 指定两个变量

    1
    2
    ARG site
    ARG build_user=IT笔录

    以上我们指定了 site 和 build_user 两个变量,其中 build_user 指定了默认值。在使用 docker build 构建镜像时,可以通过 --build-arg <varname>=<value> 参数来指定或重设置这些变量的值。

    1
    docker build --build-arg site=itiblu.com -t itbilu/test .

    这样我们构建了 itbilu/test 镜像,其中 site 会被设置为 itbilu.com,由于没有指定 build_user,其值将是默认值 IT 笔录。

2.16、STOPSIGNAL

  • 语法STOPSIGNAL signal

  • STOPSIGNAL用于设置停止容器所要发送的系统调用信号

  • 所使用的信号必须是内核系统调用表中的合法的值,如:SIGKILL。

2.17、SHELL

  • 语法SHELL ["executable", "parameters"]

  • 说明:

    • SHELL用于设置执行命令(shell式)所使用的的默认 shell 类型
    • SHELL在Windows环境下比较有用,Windows 下通常会有 cmd 和 powershell 两种 shell,可能还会有 sh。这时就可以通过 SHELL 来指定所使用的 shell 类型:
      1
      2
      3
      4
      5
      6
      7
      # Executed as powershell -command Write-Host hello
      SHELL ["powershell", "-command"]
      RUN Write-Host hello

      # Executed as cmd /S /C echo hello
      SHELL ["cmd", "/S"", "/C"]
      RUN echo hello

3、Dockerfile 例子

构建Nginx运行环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 指定基础镜像
FROM sameersbn/ubuntu:14.04.20161014

# 维护者信息
MAINTAINER sameer@damagehead.com

# 设置环境
ENV RTMP_VERSION=1.1.10 \
NPS_VERSION=1.11.33.4 \
LIBAV_VERSION=11.8 \
NGINX_VERSION=1.10.1 \
NGINX_USER=www-data \
NGINX_SITECONF_DIR=/etc/nginx/sites-enabled \
NGINX_LOG_DIR=/var/log/nginx \
NGINX_TEMP_DIR=/var/lib/nginx \
NGINX_SETUP_DIR=/var/cache/nginx

# 设置构建时变量,镜像建立完成后就失效
ARG BUILD_LIBAV=false
ARG WITH_DEBUG=false
ARG WITH_PAGESPEED=true
ARG WITH_RTMP=true

# 复制本地文件到容器目录中
COPY setup/ ${NGINX_SETUP_DIR}/
RUN bash ${NGINX_SETUP_DIR}/install.sh

# 复制本地配置文件到容器目录中
COPY nginx.conf /etc/nginx/nginx.conf
COPY entrypoint.sh /sbin/entrypoint.sh

# 运行指令
RUN chmod 755 /sbin/entrypoint.sh

# 允许指定的端口
EXPOSE 80/tcp 443/tcp 1935/tcp

# 指定网站目录挂载点
VOLUME ["${NGINX_SITECONF_DIR}"]

ENTRYPOINT ["/sbin/entrypoint.sh"]
CMD ["/usr/sbin/nginx"]

4、原则与建议

  • 容器轻量化。从镜像中产生的容器应该尽量轻量化,能在足够短的时间内停止、销毁、重新生成并替换原来的容器。
  • 使用 .gitignore。在大部分情况下,Dockerfile 会和构建所需的文件放在同一个目录中,为了提高构建的性能,应该使用 .gitignore 来过滤掉不需要的文件和目录。
  • 为了减少镜像的大小,减少依赖,仅安装需要的软件包。
  • 一个容器只做一件事。解耦复杂的应用,分成多个容器,而不是所有东西都放在一个容器内运行。如一个 Python Web 应用,可能需要 Server、DB、Cache、MQ、Log 等几个容器。一个更加极端的说法:One process per container。
  • 减少镜像的图层。不要多个 Label、ENV 等标签。
  • 对续行的参数按照字母表排序,特别是使用 apt-get install -y 安装包的时候。
  • 使用构建缓存。如果不想使用缓存,可以在构建的时候使用参数 --no-cache=true 来强制重新生成中间镜像。

5、Docker 执行 Dockerfile 的大致流程

  1. Docker 从基础镜像运行一个容器。
  2. 执行一条指令并对容器作出修改。
  3. 执行类似 docker commit 的操作提交一个新的镜像层。
  4. Docker 再基于刚提交的镜像运行一个新容器。
  5. 执行 dockerfile 中的下一条指令直到所有指令都执行完成。

DockerFile
https://flepeng.github.io/042-云原生-01-Docker-42-核心概念-DockerFile/
作者
Lepeng
发布于
2023年3月7日
许可协议