容器的本质是一个在前台运行的进程,进程启动时需要设置启动命令与参数。对 docker 而言,设置的方式则是通过 CMD 或 ENTRYPOINT,实际使用时一般是通过 Dockerfile 直接设置,也可在执行docker run 时进行指定。无论是CMD或是ENTRYPOINT,都有shell与exec两种形式。

CMD

  1. shell格式
    1. FROM busybox:1.0
    2. ENV name docker
    3. CMD echo "hello $name"
  • 执行 docker run --rm <image> 通过默认命令来启动容器,此时容器打印 hello docker 并会自动被删除(因为增加了 —rm 参数)
  • 执行 docker run --rm <image> echo "hello world" 来启动容器,此时 echo "hello world" 会覆盖默认命令,容器打印内容为 hello world

image.png

  1. exec格式
    FROM busybox:1.0
    ENV name docker
    CMD ["echo","hello $name"]
    
  • 执行 docker run --rm <image> 通过默认命令来启动容器,此时容器打印内容为 hello $name 变量并未被替换
  • 执行 docker run --rm <image> echo "hello world" 来启动容器,此时 echo "hello world" 会覆盖默认命令,容器打印内容为 hello world

image.png

  1. CMD 示例汇总
  • 无论是 shell 格式还是 exec 格式,Dockerfile 中 CMD 的指定内容均会被 docker run 命令中指定的 [COMMAND] [ARG…] 所替换

    image.png

  • exec 格式下默认命令中的变量无法被替换,原因在于 shell 格式默认会调用 bin/sh -c 来执行命令,而 exec 则不会,若想达到同样的效果可在命令中直接指明使用哪种 shell,比如 /bin/sh。将 Dockerfile 修改为如下,此时重新构建镜像,再次依据默认命令执行时,变量 $name 会被解析,容器会输出 hello docker

    FROM busybox:1.0
    ENV name docker
    CMD ["/bin/sh","-c","echo hello $name"]
    

    注意:指明 shell 时需先确定该镜像中包含有此种 shell,否则执行时会出错,比如本次实例中 busybox 镜像并未包含 bash,若指定为 bash,即 CMD 值为[“/bin/bash”,”-c”,”echo hello $name”] 运行时会提示 /bin/bash 不存在

image.png

ENTRYPOINT

  1. shell格式
    FROM busybox:1.0
    ENV name docker
    ENTRYPOINT echo "hello $name"
    
  • 执行 docker run --rm <image> 通过默认命令来启动容器,此时容器打印 hello docker
  • 执行 docker run --rm <image> echo "hello world" 来启动容器,此时 echo "hello world" 并不会覆盖默认的命令,容器打印内容依旧为 hello docker

    ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12454968/1610936120946-1f083c7e-9458-4e6e-803e-2a601e475d6d.png#align=left&display=inline&height=61&margin=%5Bobject%20Object%5D&name=image.png&originHeight=121&originWidth=1026&size=16015&status=done&style=none&width=513)<br />该示例中,ENTRYPOINT 的 shell 格式下默认命令的执行结果与 CMD 的 shell 格式并无区别,区别在于docker run 中指定的 [COMMAND] [ARG...] 所覆盖,该情况不难理解,因为 COMMAND 命令对应的是 Dockerfile 中的 CMD,要是想覆盖默认的ENTRYPOINT,需要使用 --entrypoint,比如:`docker run --rm --entrypoint echo busybox-entry:shell "hello world"`
    
  • —entrypoint 设置的命令是 echo ,其会将默认的 echo “hello $name” 所覆盖

  • 该命令中docker run 指定的 COMMAND 值为”hello world” ,此时的 COMMAND 会作为 ENTRYPOINT 的参数传入,所以真正的命令应该是 echo “hello world”

    ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12454968/1610945103154-38741891-295c-4d5f-ae88-748b3ee10c21.png#align=left&display=inline&height=76&margin=%5Bobject%20Object%5D&name=image.png&originHeight=152&originWidth=1271&size=26799&status=done&style=none&width=635.5)
    
  1. exec格式
    FROM busybox:1.0
    ENV name docker
    ENTRYPOINT ["echo","hello $name"]
    
  • 执行 docker run --rm <image> 通过默认命令来启动容器,此时容器打印 hello $name 变量未被替换
  • 执行 docker run --rm <image> echo "hello world" 来启动容器,此时的 echo "hello world" 会作为参数,容器打印内容为 hello world

    image.png

  1. ENTRYPOINT 示例汇总
  • shell 格式下命令中的变量会被解析,exec 格式则不会
  • shell 格式下,docker run 指定的 COMMAND 对默认的 ENTRYPOINT 命令无影响;exec 格式下,COMMAND 的内容会被当做参数传给默认的ENTRYPOINT命令
  • 无论是 shell 格式还是 exec 格式,都可在 docker run 时通过 —entrypoint 来替换默认的 ENTRYPOINT 命令

    使用汇总

  1. shell 格式 | CMD | ENTRYPOINT | docker run | 实际命令 | 备注 | | —- | —- | —- | —- | —- | | echo “hello cmd” | 未设置 | echo “hello run” | echo “hello run” | 被 docker run 指定的 COMMAND 替换 | | 未设置 | echo “hello entrypoint” | echo “hello run” | echo “hello entrypoint” | 不会被替换 | | echo “hello cmd” | echo “hello entrypoint” | 未设置 | echo “hello entrypoint” | 不会被替换 | | echo “hello cmd” | echo “hello entrypoint” | echo “hello run” | echo “hello entrypoint” | 不会被替换 |

  2. exec 格式 | CMD | ENTRYPOINT | docker run | 实际命令 | 备注 | | —- | —- | —- | —- | —- | | [“echo”,”hello cmd”] | 未设置 | [“echo”,”hello run”] | [“echo”,”hello run”] | 被 docker run 指定的 COMMAND 替换 | | 未设置 | [“echo”,”hello entrypoint”] | [“echo”,”hello run”] | [“echo”,”hello entrypoint echo hello run”] | COMMAND 内容当做参数传给ENTRYPOINT | | [“echo”,”hello cmd”] | [“echo”,”hello entrypoint”] | 未设置 | [“echo”,”hello entrypoint echo hello cmd”] | CMD 内容当做参数传给ENTRYPOINT | | [“echo”,”hello cmd”] | [“echo”,”hello entrypoint”] | [“echo”,”hello run”] | [“echo”,”hello entrypoint echo hello run”] | COMMAND 替换CMD 内容后当做参数传给ENTRYPOINT |

  3. 使用建议

  • 灵活使用 docker run 中的 COMMAND 和 —entrypoint 来进行调试。比如容器本身进程无法正常运行,可在docker run 时指定一个前台运行的进程(个人常用tail -f /etc/hosts),再进入到容器内部来进行调试
  • 若容器启动命令包含太多参数,尽量使用 ENTRYPOINT 与 CMD 结合的方式,CMD 中指定参数,这样方便在使用 docker run 时进行参数替换