RUN

RUN指令指定的命令在docker build构建镜像的时候执行。常用于安装软件包。可以有多个RUN指令,且每个都生效。

Dockerfile 文件
1
2
3
FROM ubuntu:18.04
RUN apt update && apt install -y vim
RUN touch a

执行docker build构建镜像时会在一层镜像层中更新源并安装vim命令,在另一个镜像层中创建a文件。

注意:apt updateapt install被放在一个RUN指令中执行,这样能够保证每次安装的是最新的包。如果apt install在单独的RUN中执行,则会使用apt update创建的镜像层,而这一层可能是很久以前缓存的。

CMD && ENTRYPOINT

CMD指令指定的命令在容器启动后执行。可以有多个CMD指令,但只有最后一个生效。CMD指定的命令会被docker run之后的参数替换。

Dockerfile 文件
1
2
3
FROM ubuntu:trusty
CMD hostname
CMD ping localhost

执行docker build -t ubuntu1 .构建一个ubuntu1镜像。执行docker run -t ubuntu1

root# docker run -t ubuntu1
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.031 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.037 ms
^C

可以看出虽然Dockerfile文件中指定了两个CMD指令,但只有最后一个CMD指令执行,ping命令在容器启动后执行。

尽管指定了CMD指令,但我们可以在启动容器时, 执行其他命令行参数, 覆盖默认的CMD指令:

root# docker run -t ubuntu1 hostname
59cd3e4806ee

容器启动后, 并没有执行ping命令, 而是执行了hostname命令。

CMD指令类似ENTRYPOINT指令指定的命令也在容器启动后执行。也可以有多个ENTRYPOINT指令,也是只有最后一个生效。也可以被覆盖,在运行时使用--entrypoint覆盖默认值。

Dockerfile 文件
1
2
3
FROM ubuntu:trusty
ENTRYPOINT hostname
ENTRYPOINT ping localhost

执行docker build -t ubuntu2 .构建一个ubuntu2镜像。执行docker run -t ubuntu2

root# docker run -t ubuntu2
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.024 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.039 ms
^C

执行docker run -t --entrypoint hostname ubuntu2

root# docker run -t --entrypoint hostname ubuntu2
30a2145449c3

从根本上说,CMD指令和ENTRYPOINT指令都是让用户指定一个可执行程序, 这个可执行程序在容器启动后自动执行。实际上,如果你想让自己制作的镜像自动运行程序(不需要在docker run后面添加命令行指定运行的命令),你必须在Dockerfile里面使用CMD指令或ENTRYPOINT指令。如果运行一个没有调用CMD指令或ENTRYPOINT指令的镜像, 一定返回错误:

Dockerfile 文件
1
2
FROM scratch
ADD alpine-minirootfs-3.9.3-x86_64.tar.gz /

执行docker build -t alpine .构建一个alpine镜像。执行docker run -t alpine

root# docker run -t alpine
docker: Error response from daemon: No command specified.
See 'docker run --help'.

Shell && Exec

RUNCMDENTRYPOINT都支持两种形式的写法:ShellExec。二者在使用上有细微的区别。

Shell 格式
1
<instruction> <command>

当指令执行时,Shell格式的写法底层会调用/bin/sh -c <command>

Dockerfile 片段
1
2
ENV WORLD world
ENTRYPOINT echo "Hello $WORLD"

容器启动后将输出:Hello world

Exec 格式
1
<instruction> ["executable", "param1", "param2", ...]

当指令执行时,Exec格式的写法会直接调用<command>,不会被shell解析。

Dockerfile 片段
1
2
ENV WORLD world
ENTRYPOINT ["/bin/echo", "Hello $WORLD"]

容器启动后将输出:Hello $WORLD

注意环境变量WORLD没有被替换。如果希望环境变量被替换,修改为:

Dockerfile 片段
1
2
ENV WORLD world
ENTRYPOINT ["/bin/sh", "-c", "echo Hello $WORLD"]

尽管CMDENTRYPOINT都支持两种形式的写法,但更推荐使用Exec格式。RUN则两种格式都可以。

CMD 作为参数传递给 ENTRYPOINT

上面说过尽管CMDENTRYPOINT都支持两种形式的写法,但更推荐使用Exec格式。因为当CMD作为参数为ENTRYPOINT提供额外的参数时,ENTRYPOINT必须使用Exec格式才能接收到参数。

Dockerfile 片段
1
2
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]

执行docker run <image>启动容器后将输出:Hello world。执行docker run <image> 世界启动容器后将输出:Hello 世界

Dockerfile 片段
1
2
ENTRYPOINT "echo Hello"
CMD ["world"]

执行docker run <image>启动容器后将输出:Hello。执行docker run <image> 世界启动容器后将输出:HelloENTRYPOINTShell格式会忽略任何CMDdocker run提供的参数。