Back to Docker Practice

7.5 Entrypoint

07_dockerfile/7.5_entrypoint.md

1.9.07.5 KB
Original Source

7.5 ENTRYPOINT 入口点

何时使用 ENTRYPOINT:从“容器”到“命令”

如果说 CMD 是“容器中的默认程序”,那么 ENTRYPOINT 就是“把容器变成一个命令”。这个思维转变决定了你何时使用 ENTRYPOINT。

使用 ENTRYPOINT 的典型场景

  1. 命令行工具:你想让镜像像 curlwget 一样使用

    dockerfile
    ENTRYPOINT ["curl"]
    # docker run myimage http://example.com → curl http://example.com
    
  2. 应用启动脚本:你有一个初始化脚本,需要接收命令行参数

    dockerfile
    ENTRYPOINT ["/app/entrypoint.sh"]
    # docker run myimage --debug → /app/entrypoint.sh --debug
    
  3. 与 CMD 结合:ENTRYPOINT 定义入口,CMD 定义默认参数

    dockerfile
    ENTRYPOINT ["python", "app.py"]
    CMD ["--port", "8000"]
    # docker run myimage → python app.py --port 8000
    # docker run myimage --port 9000 → python app.py --port 9000
    

对比 CMD:如果没有这些“把容器当命令用”的需求,通常使用 CMD 就足够了。

7.5.1 什么是 ENTRYPOINT

ENTRYPOINT 指定容器启动时运行的入口程序。与 CMD 不同,ENTRYPOINT 定义的命令不会被 docker run 的参数覆盖,而是 接收这些参数

核心作用:让镜像像一个可执行程序一样使用,docker run 的参数作为这个程序的参数。


7.5.2 语法格式

格式语法推荐程度
exec 格式ENTRYPOINT [“可执行文件”, “参数1”]推荐
shell 格式ENTRYPOINT 命令 参数⚠️ 不推荐
docker
## exec 格式(推荐)

ENTRYPOINT ["nginx", "-g", "daemon off;"]

## shell 格式(不推荐)

ENTRYPOINT nginx -g "daemon off;"

7.5.3 ENTRYPOINT vs CMD

核心区别

特性ENTRYPOINTCMD
定位固定的入口程序默认参数
docker run 参数追加为参数完全覆盖
覆盖方式--entrypoint直接指定命令
适用场景把镜像当命令用提供默认行为

行为对比

docker
## 只用 CMD

CMD ["curl", "-s", "http://example.com"]
bash
$ docker run myimage              # curl -s http://example.com
$ docker run myimage -v           # 执行 -v(错误!)
$ docker run myimage curl -v ...  # curl -v ...(完全替换)
docker
## 只用 ENTRYPOINT

ENTRYPOINT ["curl", "-s"]
bash
$ docker run myimage                      # curl -s(缺参数)
$ docker run myimage http://example.com   # curl -s http://example.com ✓
docker
## ENTRYPOINT + CMD 组合(推荐)

ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
bash
$ docker run myimage                      # curl -s http://example.com(默认)
$ docker run myimage http://other.com     # curl -s http://other.com ✓
$ docker run myimage -v http://other.com  # curl -s -v http://other.com ✓

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

需求:启动前准备

创建一个查询公网 IP 的 “命令” 镜像。

使用 CMD 的问题

docker
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
CMD ["curl", "-s", "http://myip.ipip.net"]
bash
$ docker run myip           # ✓ 正常工作
当前 IP:61.148.226.66

$ docker run myip -i        # ✗ 错误!
exec: "-i": executable file not found

## -i 替换了整个 CMD,被当作可执行文件

...

使用 ENTRYPOINT 解决

docker
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
bash
$ docker run myip           # ✓ 正常工作
当前 IP:61.148.226.66

$ docker run myip -i        # ✓ 添加 -i 参数
HTTP/1.1 200 OK
...
当前 IP:61.148.226.66

交互图示

bash
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
            │
docker run myip -i
            │
            ▼
curl -s http://myip.ipip.net -i
└─────────────────────────────┘
     ENTRYPOINT + docker run 参数

7.5.5 场景二:启动前的准备工作

需求

在启动主服务前执行初始化脚本 (如数据库迁移、权限设置)。

实现方式

docker
# 建议使用 redis:7 或 redis:latest,具体版本号根据生产需求选择
FROM redis:7-alpine
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]

docker-entrypoint.sh

bash
#!/bin/sh
set -e

## 准备工作

echo "Initializing..."

## 如果第一个参数是 redis-server,以 redis 用户运行

if [ "$1" = 'redis-server' ]; then
    chown -R redis:redis /data
    exec gosu redis "$@"
fi

## 其他命令直接执行

exec "$@"

工作流程

bash
docker run redis                    docker run redis bash
        │                                    │
        ▼                                    ▼
docker-entrypoint.sh redis-server   docker-entrypoint.sh bash
        │                                    │
        ├─ 初始化                            ├─ 初始化
        ├─ chown -R redis:redis /data        │
        └─ exec gosu redis redis-server      └─ exec bash
           (以 redis 用户运行)                  (以 root 用户运行)

关键点

  1. exec “$@”:用传入的参数替换当前进程,确保信号正确传递
  2. 条件判断:根据 CMD 不同执行不同逻辑
  3. 用户切换:使用 gosu 切换用户 (比 su 更适合容器)

7.5.6 场景三:带参数的应用

docker
# 建议使用 python:3.12 或 python:3,具体版本号根据应用兼容性需求选择
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt

ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8080"]
bash
## 使用默认参数

$ docker run myapp

## 执行: python app.py --host 0.0.0.0 --port 8080

## 覆盖参数

$ docker run myapp --host 0.0.0.0 --port 9000

## 执行: python app.py --host 0.0.0.0 --port 9000

## 完全不同的参数

$ docker run myapp --help

## 执行: python app.py --help

...

7.5.7 覆盖 ENTRYPOINT

使用 --entrypoint 参数覆盖:

bash
## 正常运行

$ docker run myimage

## 覆盖 ENTRYPOINT 进入 shell 调试

$ docker run --entrypoint /bin/sh myimage

## 覆盖 ENTRYPOINT 并传入参数

$ docker run --entrypoint /bin/cat myimage /etc/os-release

7.5.8 ENTRYPOINT 与 CMD 组合表

ENTRYPOINTCMD最终执行命令
无 (容器无法启动)
["cmd", "p1"]cmd p1
["ep", "p1"]ep p1
["ep", "p1"]["cmd", "p2"]ep p1 cmd p2
ep p1 (shell)["cmd", "p2"]/bin/sh -c "ep p1" (CMD 被忽略)

⚠️ 注意:shell 格式的 ENTRYPOINT 会忽略 CMD!


7.5.9 最佳实践

1. 使用 exec 格式

docker
## ✅ 推荐

ENTRYPOINT ["python", "app.py"]

## ❌ 避免 shell 格式

ENTRYPOINT python app.py

2. 提供有意义的默认参数

docker
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

3. 入口脚本使用 exec

bash
#!/bin/sh

## 准备工作...

## 使用 exec 替换当前进程

exec "$@"

4. 处理信号

确保 ENTRYPOINT 脚本能正确传递信号:

bash
#!/bin/bash
trap 'kill -TERM $PID' TERM INT

## 启动应用

app "$@" &
PID=$!

## 等待应用退出

wait $PID