ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。

    当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

    那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 <ENTRYPOINT> "<CMD>" 有什么好处么?让我们来看几个场景。

    场景一:让镜像变成像命令一样使用

    假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD 来实现:

    1. FROM ubuntu:18.04
    2. RUN apt-get update \
    3. && apt-get install -y curl \
    4. && rm -rf /var/lib/apt/lists/*
    5. CMD [ "curl", "-s", "https://ip.cn" ]

    假如我们使用 docker build -t myip . 来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行:

    1. $ docker run myip
    2. 当前 IP61.148.226.66 来自:北京市 联通

    我们可以看到可执行文件找不到的报错,executable file not found。之前我们说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。因此这里的 -i 替换了原来的 CMD,而不是添加在原来的 curl -s https://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。

    那么如果我们希望加入 -i 这参数,我们就必须重新完整的输入这个命令:

    1. $ docker run myip curl -s https://ip.cn -i

    这显然不是很好的解决方案,而使用 ENTRYPOINT 就可以解决这个问题。现在我们重新用 ENTRYPOINT 来实现这个镜像:

    1. RUN apt-get update \
    2. && apt-get install -y curl \
    3. && rm -rf /var/lib/apt/lists/*
    4. ENTRYPOINT [ "curl", "-s", "https://ip.cn" ]

    这次我们再来尝试直接使用 docker run myip -i

    可以看到,这次成功了。这是因为当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到了我们预期的效果。

    场景二:应用运行前的准备工作

    比如 mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。

    此外,可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 root 身份执行,方便调试等。

    这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 <CMD>)作为命令,在脚本最后执行。比如官方镜像 redis 中就是这么做的:

    1. FROM alpine:3.4
    2. ...
    3. RUN addgroup -S redis && adduser -S -G redis redis
    4. ...
    5. ENTRYPOINT ["docker-entrypoint.sh"]
    6. CMD [ "redis-server" ]

    可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINTdocker-entrypoint.sh 脚本。

    1. #!/bin/sh
    2. ...
    3. # allow the container to be started with `--user`
    4. if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    5. chown -R redis .
    6. exec su-exec redis "$0" "$@"
    7. fi
    8. exec "$@"

    该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 用户身份启动服务器,否则依旧使用 root 身份执行。比如: