不推荐在 Dockerfile 的 CMD 中直接启动多个服务,因违背“一个容器一个主进程”原则,应优先拆分为多容器并用 docker-compose 编排;若必须单容器多服务,可用 supervisord 管理且须作为 PID 1 运行;禁用 shell 脚本粗暴启停方式。

不推荐在 Dockerfile 的 CMD 中直接启动多个服务(比如同时跑 Nginx + PHP-FPM + Redis),因为容器设计哲学是“一个容器一个主进程”,多服务混跑会破坏可观察性、信号传递、生命周期管理,也违背分层隔离原则。
用进程管理器统一托管(如 supervisord)
若必须单容器多服务(例如遗留系统迁移或开发环境快速验证),可用轻量级进程管理器接管子进程,确保信号转发和日志收集:
- 安装
supervisor(Alpine 或 Debian 均有包) - 编写
/etc/supervisord.conf,为每个服务定义独立 program 段,设置autostart=true、autorestart=true、startsecs=0(避免健康检查误判) CMD ["supervisord", "-c", "/etc/supervisord.conf"]- 注意:supervisord 必须作为 PID 1 运行,否则无法响应
SIGTERM;建议禁用其内部 web server 和 RPC 接口,减少攻击面
拆分为多个容器,用 docker-compose 编排
生产环境的标准做法:每个服务独占容器,通过 docker-compose.yml 关联网络与卷:
- Web 服务容器只运行 Nginx,PHP 逻辑通过
fastcgi_pass转发到单独的 php-fpm 容器 - 数据库、缓存等均独立容器,用 service 名作主机名(如
redis://redis:6379) - 共享配置可通过
volumes挂载,或使用configs(Swarm)/env_file管理环境变量 - 健康检查、重启策略、资源限制可按服务粒度单独配置
用 shell 脚本协调启动(仅限简单场景)
若服务间依赖明确且无需复杂进程控制(如一个前台服务 + 一个后台守护进程),可用自定义 entrypoint.sh:
- 脚本中用
&启动后台服务(如redis-server &),记录 PID - 用
wait -n或轮询检测关键服务就绪(如curl -f http://localhost:80/health) - 最后
exec "$@"执行 CMD 命令(通常是前台 Web 服务),使其成为 PID 1 - 务必 trap
SIGTERM并主动停止子进程,否则容器 kill 时子服务可能残留
避免的写法
以下方式存在严重问题,应杜绝:
-
CMD ["sh", "-c", "service nginx start && service php7.4-fpm start && tail -f /dev/null"]—— 子进程脱离 shell 控制,无法接收信号,日志丢失,退出状态不可靠 -
CMD ["bash", "-c", "nginx & php-fpm & wait"]——wait只等待直接子进程,且 bash 不是 PID 1,信号无法透传 - 在 CMD 中调用
systemctl或service—— 大多数基础镜像无 systemd,init 系统缺失导致命令失败










