最稳妥的方式是用自定义 entrypoint 脚本,在主进程启动前完成初始化并保留原 cmd 执行权和信号处理能力;脚本需以 #!/bin/sh 开头、设可执行权限、校验每步返回值、结尾用 exec "$@" 确保主进程为 pid 1,dockerfile 中通过 copy 和 entrypoint ["/entrypoint.sh"] 集成。
最稳妥的方式是用自定义 entrypoint 脚本,在主进程启动前完成初始化,同时保留原 cmd 的执行权和信号处理能力。
用 ENTRYPOINT 脚本接管启动流程
这是生产环境推荐做法:把初始化逻辑写进一个 shell 脚本,设为容器的 ENTRYPOINT,最后用 exec "$@" 启动原命令。
- 脚本开头必须写
#!/bin/sh(或#!/bin/bash),并赋予可执行权限:chmod +x entrypoint.sh - 在脚本中做实际初始化操作,比如:
sed -i "s/DB_HOST/$DB_HOST/" /app/config.yml、chown -R app:app /data - 所有关键步骤都要检查返回值,失败时
exit 1,避免容器“假启动” - 结尾必须是
exec "$@",确保原 CMD 或运行时指定的命令成为 PID 1 进程,能正常接收 SIGTERM 等信号 - Dockerfile 中写:
COPY entrypoint.sh /entrypoint.sh和ENTRYPOINT ["/entrypoint.sh"]
在 docker-compose 中等待依赖服务就绪
单纯靠启动顺序不够——depends_on 只等容器起来,不等服务可用。需要主动探测。
- 在 entrypoint.sh 里加入轮询逻辑,例如:
until nc -z db 5432; do sleep 2; done - 更健壮的做法是调用
wait-for-it.sh这类轻量工具(需提前 COPY 进镜像) - 配合 docker-compose.yml 中的
healthcheck,让depends_on: { condition: service_healthy }真正生效 - 数据库类服务建议配 healthcheck,如 PostgreSQL 可用:
test: ["CMD-SHELL", "pg_isready -U postgres"]
慎用 CMD 包裹初始化命令
只适合极简场景,比如改个文件权限、设个环境变量,且无错误重试需求。
- 写法示例:
CMD sh -c "chmod 600 /root/.ssh/id_rsa && exec /usr/local/bin/myapp" - 必须用
exec替换当前 shell,否则主进程是 shell,收不到终止信号 - 无法做多步判断、日志输出不清晰、出错后难以调试,不适合复杂初始化
- 一旦覆盖了 CMD,就丢失了镜像原本的启动语义,不利于复用和维护
避免常见陷阱
几个容易踩坑但影响严重的点:
- 脚本路径写相对路径(如
./entrypoint.sh)——Docker 要求绝对路径 - 忘记加
exec,导致 PID 1 不是主程序,容器无法优雅停止 - 初始化脚本后台启动服务(如
service nginx start &)——会脱离进程树,被 init 杀死 - 没校验命令是否成功,比如
mysql -h db -e "CREATE DATABASE..."失败也不退出 - 在 Alpine 镜像中用了 bash 特性但基础镜像是
sh——应统一用 POSIX shell 写法











