Dockerfile 之 RUN/CMD/ENTRYPOINT 指令
RUN
RUN指令指定的命令在docker build构建镜像的时候执行。常用于安装软件包。可以有多个RUN指令,且每个都生效。
1 | FROM ubuntu:18.04 |
执行docker build构建镜像时会在一层镜像层中更新源并安装vim命令,在另一个镜像层中创建a文件。
注意:apt update和apt install被放在一个RUN指令中执行,这样能够保证每次安装的是最新的包。如果apt install在单独的RUN中执行,则会使用apt update创建的镜像层,而这一层可能是很久以前缓存的。
CMD && ENTRYPOINT
CMD指令指定的命令在容器启动后执行。可以有多个CMD指令,但只有最后一个生效。CMD指定的命令会被docker run之后的参数替换。
1 | FROM ubuntu:trusty |
执行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覆盖默认值。
1 | FROM ubuntu:trusty |
执行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指令的镜像, 一定返回错误:
1 | FROM scratch |
执行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
RUN、CMD和ENTRYPOINT都支持两种形式的写法:Shell和Exec。二者在使用上有细微的区别。
1 | <instruction> <command> |
当指令执行时,Shell格式的写法底层会调用/bin/sh -c <command>。
1 | ENV WORLD world |
容器启动后将输出:Hello world。
1 | <instruction> ["executable", "param1", "param2", ...] |
当指令执行时,Exec格式的写法会直接调用<command>,不会被shell解析。
1 | ENV WORLD world |
容器启动后将输出:Hello $WORLD。
注意环境变量WORLD没有被替换。如果希望环境变量被替换,修改为:
1 | ENV WORLD world |
尽管CMD和ENTRYPOINT都支持两种形式的写法,但更推荐使用Exec格式。RUN则两种格式都可以。
CMD 作为参数传递给 ENTRYPOINT
上面说过尽管CMD和ENTRYPOINT都支持两种形式的写法,但更推荐使用Exec格式。因为当CMD作为参数为ENTRYPOINT提供额外的参数时,ENTRYPOINT必须使用Exec格式才能接收到参数。
1 | ENTRYPOINT ["/bin/echo", "Hello"] |
执行docker run <image>启动容器后将输出:Hello world。执行docker run <image> 世界启动容器后将输出:Hello 世界。
1 | ENTRYPOINT "echo Hello" |
执行docker run <image>启动容器后将输出:Hello。执行docker run <image> 世界启动容器后将输出:Hello。ENTRYPOINT的Shell格式会忽略任何CMD或docker run提供的参数。