Dockerfile 指令详解

在上一节的 Dockerfile 例子中,我们用到了几个指令,比如:FROM, MAINTAINER,RUN,EXPOSE等等,Dockerfile 构建镜像需要用到的指令当然不止这些,下面是我们将介绍常用的构建指令 。

1. FROM:指明当前的镜像基于哪个镜像构建

用法:

FROM <基础镜像:版本>

示例:

FROM alpine:latest

写上这一行指令后,我们的 Dockerfile 就可以构建镜像了,构建出的镜像就是未做任何修改、没有执行任何命令的 alpine:latest

在 Docker 官方仓库里有很多高质量的服务镜像如 redis、mongo、mysql、httpd、php、tomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 node、openjdk、python、ruby、golang 等。我们可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntu、debian、centos、fedora、alpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。尽可能使用当前官方仓库作为构建镜像的基础。

2. LABEL: 标记镜像信息

给镜像添加标签来标记镜像信息,每个标签一行。

用法:

LABEL <标签>=<描述>

示例:

LABEL MYLABEL="First Test"

3. MAINTAINER:指定镜像的作者信息,包含镜像的所有者和联系人信息

用法:

MAINTAINER <NAME>

示例:

MAINTAINER nickname@domain.com

这是一种语义化的表达方式,也可以用LABEL来标记

LABEL maintainer="nickname@domain.com"

4. RUN : 运行命令

用法:

RUN <命令>

示例:

RUN echo 'text' > test.txt

为了保持 Dockerfile 文件的可读性,以及可维护性,建议将长的或复杂的RUN指令用反斜杠分割成多行。

例如:

RUN apt update && apt install -y 
      vim 
      emacs

这里需要注意一个关于软件源更新安装软件的问题。

如果我们需要更新软件源并安装软件源中的软件vim,在Linux环境中我们一般会执行这个的命令:

apt update
apt install -y vim

如果需要在镜像中安装软件,我们会想当然地在 Dockerfile 写成这样

RUN apt update
RUN apt install -y vim

Dockerfile 构建一次之后,apt update 构建的镜像层就会缓存到本地,无论后面这个 Dockerfile 如何更新 apt install 的内容,apt update 镜像缓存也不会更新,这会导致安装的始终是第一次 Dockerfile构建时获取的软件源版本,除非你手动删除这些缓存镜像层。

解决的方法很简单,用 RUN apt-get update && apt-get install -y 可以确保 Dockerfiles 每次安装的都是包的最新的版本。

5. CMD:指定容器的默认执行的命令。

建议用法:

CMD ["可执行命令", "参数1", "参数2"...]

示例:

CMD ["echo" "hello"]

docker run 没有指定其他命令时,CMD 指令会在容器执行。Dockerfile 中 CMD 只能有一个,如果写了多个 CMD,则以最后一个为准。

Tips:ENTRYPOINT 与 CMD 类似,但不会被 docker run 指定的命令覆盖。

6. EXPOSE:指定容器将要监听的端口

用法:

EXPOSE 端口号

示例:

EXPOSE 8080

启动容器时,如果我们使用自动映射 -P--net=host 宿主机网络模式,容器中 EXPOSE 标记暴露的端口与宿主机网络会自动建立关联。

如果没有指定 EXPOSE,使用 -p 手动指定端口映射参数也可以访问到容器内提供服务的端口。

EXPOSE 显式地标明镜像开放端口,一定程度上提供了操作的便利,也提高了 Dockerfile 的可读性和可维护性。

7. ENV:定义环境变量

用法:

ENV 环境变量名 环境变量值

示例:

ENV PATH /usr/local/nginx/bin:$PATH

Tips:

  1. 通过 ENV 定义的环境变量,可以被后面的所有指令中使用,但是不能被 CMD 指令使用。
  2. 通过 ENV 定义的环境变量,会永久的保存到该镜像创建的任何容器中,我们可以在 docker run 命令中通过 -e 标记来传递环境变量,启动的容器将会使用我们指定的变量值。
  3. ARG 指令与 ENV 作用基本一致,区别在于它仅在构建过程中使用,不会保留到容器中。

8. COPY: 将宿主机文件拷贝到镜像中

用法:

COPY <宿主机文件路径> <镜像文件路径>

示例:

COPY app.py  /web/

除了指定完整的文件名外,COPY 命令还支持 Go 风格的通配符,比如:

# * 是任意字符的占位符,匹配文件 test11 test22
COPY test* /tmp
# ? 是单个字符的占位符,匹配文件 test1.txt test2.txt
COPY test?.txt /tmp

对于目录而言,COPY 只复制目录中的内容而不包含目录自身。 如下目录结构:

testdir/
├── file1
└── file2
COPY testdir /tmp

镜像的 /tmp 目录下,将得到这样的文件结构:

tmp/
├── file1
└── file2

如果要带目录拷贝到镜像中,需要使用:

COPY testdir /tmp/testdir

ADDCOPY用法类似,一般优先使用 COPY。COPY 只支持简单将本地文件拷贝到容器中,而 ADD 还有从压缩包中提取文件的功能,如:

# 宿主机压缩包test.tar 解压到 镜像/tmp中
ADD test.tar /tmp

9. VOLUME:指定目录为数据卷存储方式

为了防止运行时用户忘记将需要保存数据的目录挂载为卷,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也不会向容器存储层写入大量数据。

用法:

VOLUME ["<路径1>", "<路径2>"...] 

示例:

VOLUME ["/data"]

这里的 /data 目录就会在运行时自动挂载为匿名卷,容器运行时使用 -v mydata:/data 可以覆盖这个挂载设置。

10. USER:指定运行容器时的用户名或 UID

用法:

USER <user>[:<group>]USER <UID>[:<GID>]

示例:

USER www

当容器中运行的服务不需要管理员权限时,可以先建立一个特定的用户和用户组,为它分配必要的权限,然后通过该命令,使用 USER 切换到这个用户。

Tips:

  • 使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。
  • 使用USER指定用户后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户。

我们可以在docker run中使用-u参数指定用户执行命令,来替代默认设定,如果为了精确控制用户的id,也可以传入uid。

docker run -i -t -u 1001 busybox sh

11. WORKDIR: 切换到镜像中的指定路径

WORKDIR中需要使用绝对路径,如果镜像中对应的路径不存在,会自动创建此目录。

我们使用 WORKDIR 来替代 RUN cd <path> && <do something>的这类切换目录进行操作的指令。

WORKDIR指令对ADD COPY等指令也生效,如下操作会将宿主机的test.txt 文件复制到 镜像的/tmp/test.txt。

WORKDIR /tmp
COPY test.txt .

12. ONBUILD:引用后构建指令

用法:

ONBUILD <其他指令>

示例:

ONBUILD COPY . /tmp/

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等。

FROM alpine:latest
ONBUILD RUN mkdir /app
ONBUILD COPY . /app/
CMD [ "echo", "hello" ]

使用上面 Dockerfile 在构建基础镜像的时候,这两行 ONBUILD 并不会被执行。它的效果等价于:

FROM alpine:latest
CMD [ "echo", "hello" ]

构建出来的镜像作为基础镜像,在其他 Dockerfile 的 FROM 指令中被引用,去构建新镜像的时候,ONBUILD 后的指令会执行。

13. 小结

本节介绍了我们使用 Dockerfile 构建镜像的过程中经常会用到的指令,这些指令大多简单易懂的。但本节中也提到,其中有些指令的用法比较特殊,在某些看似“理所当然”的使用方法下,可能会出现"bug",请大家一定要留意。后面也有实战章节,帮助大家加深理解。