尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PHP容器启动即退出?揭秘php-latest镜像的默认行为与稳定实践

PHP容器启动即退出?揭秘php-latest镜像的默认行为与稳定实践 前言典型症状是这样的执行docker run php命令几乎瞬间返回docker ps里什么都没有docker ps -a能看到容器状态是Exited (0)docker logs一行输出都没有。换成docker run -it php却一切正常能进到一个php 提示符。于是很多人得出一个错误结论PHP 镜像坏了起不来。真相是官方 PHP 镜像的默认命令本来就是php -a交互式 REPL。交互式 shell 会一直等着从标准输入读命令而docker run默认不把终端接进去stdin 立刻是 EOFREPL 读到文件结尾就正常退出——退出码 0没有任何错误。容器不是崩溃了它只是把该做的事做完了。要彻底理解这件事需要同时接受两个事实容器不是一个会自己待机的虚拟机容器里 PID 1 的进程一旦退出容器就结束而latest只是一个跟随当前稳定分支的浮动标签它的行为并不是启动一个 PHP 服务。本文会把默认行为讲清楚再给出按用途选镜像、让容器稳定运行的实践方案含 compose 与 nginx 组合最后列出几个真会踩的坑。一、根因默认命令是php -a而容器只认 PID 1官方 CLI 变体镜像如php:8.4-cli的CMD是php -a。这个参数的作用是进入交互模式启动方式stdin表现docker run php未接入EOF立即退出退出码 0无日志docker run -i php接入等待输入输入结束才退出docker run -it php接入且是 TTY出现php 提示符可以敲代码docker run -d php php -S 0.0.0.0:8000 -t /app不依赖常驻正常对外服务关键点在于命令的位置docker run 镜像 命令...中镜像名之后的所有内容会完全取代镜像里的CMD。所以docker run -d php php -S ...是用这个镜像跑一个内置服务器而不是启动这个镜像。同理docker stop想把容器停下来靠的是给 PID 1 发SIGTERM。如果 PID 1 是一个 shell 包装脚本而不是真正的服务进程信号根本送不到服务那里只能等 10 秒超时被SIGKILL。这就是exec的重要性和它背后的原因。二、按用途选镜像cli / fpm / apache 各是什么php:latest指向当前最新稳定分支的 CLI 变体具体是哪个小版本取决于你拉取的时刻不要在生产环境依赖它。正确做法是按用途选明确的标签标签示例默认命令用途是否常驻php:8.4-cliphp -a跑脚本、CLI 任务、内置服务器否需显式命令php:8.4-fpmphp-fpm配合 nginx 处理 HTTP是监听 9000php:8.4-apacheapache2-foreground一体化 Web 环境是php:8.4-cli-alpinephp -a体积更小的 CLI否需显式命令两个常见误解一是以为-fpm镜像自带 Web 服务器——它只提供 FastCGI 服务监听 9000 端口前面必须有一个能转发 FastCGI 的服务器二是以为-cli不能做 Web——它可以用php -S起内置服务器就够开发用了只是性能和特性都不适合生产。三、稳定实践给容器一个前台常驻进程三条原则进程必须前台运行不要写成后台守护、必须是PID 1、必须能收到信号。最简的开发环境直接用内置服务器docker run -d --name php-dev \ -p 8000:8000 \ -v $PWD/src:/app \ -w /app \ php:8.4-cli \ php -S 0.0.0.0:8000 -t /app注意绑定地址必须是0.0.0.0而不是127.0.0.1否则容器外永远访问不到。生产环境用 php-fpm nginx 的组合services: php: image: php:8.4-fpm-alpine volumes: - ./src:/var/www/html working_dir: /var/www/html healthcheck: test: [CMD, php-fpm, -t] interval: 30s timeout: 5s retries: 3 web: image: nginx:1.27-alpine ports: - 8080:80 volumes: - ./src:/var/www/html:ro - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - php对应的 nginx 配置fastcgi_pass直接写 compose 里的服务名容器网络内可解析server { listen 80; server_name _; root /var/www/html; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass php:9000; fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; } }如果需要在镜像里装扩展、调 ini注意官方镜像的配置目录是/usr/local/etc/php/conf.d/而不是常见的/etc/php.iniFROM php:8.4-fpm-alpine RUN docker-php-ext-install pdo_mysql \ docker-php-ext-enable opcache COPY conf.d/app.ini /usr/local/etc/php/conf.d/app.ini WORKDIR /var/www/html; conf.d/app.ini memory_limit 256M upload_max_filesize 20M post_max_size 24M自定义入口脚本时结尾一定要用exec把控制权交给真正的服务进程#!/bin/sh set -e if [ ! -f /var/www/html/.env ]; then cp /var/www/html/.env.example /var/www/html/.env fi exec $ # 让服务进程成为 PID 1才能收到 SIGTERM常见坑点1. 以为docker run php会得到一个 PHP 服务❌ 起完就去找端口、翻日志结果容器早就Exited (0)。 ✅ 想跑什么就显式写在镜像名后面想验证镜像本身用docker run -it --rm php php -v。2. 入口脚本结尾不加exec❌#!/bin/sh里最后一行是php-fpm——PID 1 是 shelldocker stop的SIGTERM送不到 php-fpm每次停止都要硬等 10 秒后被SIGKILL正在处理的请求被直接掐断。 ✅ 用exec $或exec php-fpm。3. 脚本在 Windows 上编辑后保存成 CRLF❌ 挂载进去的entrypoint.sh每行结尾是\r\n启动时报standard_init_linux.go: exec format error看起来像是镜像或架构问题。 ✅ 保存为 LF.gitattributes里加*.sh text eollf再补一行set -e让错误尽早暴露。4. 内置服务器绑了127.0.0.1❌php -S 127.0.0.1:8000——容器里监听回环地址宿主机-p 8000:8000也转不进去curl直接 connection reset。 ✅ 绑0.0.0.0。5. 把 php-fpm 镜像当 Web 服务器用❌ 只起php:8.4-fpm映射9000:9000然后用浏览器访问——端口不返回 HTTP 响应。 ✅ 前面配 nginx或 Apache mod_proxy_fcgifastcgi_pass指向 fpm 服务名。6. 用:latest又不锁版本❌ 今天构建成功明天基础镜像更新到新分支扩展编译失败或行为变化CI 直接红掉而代码一行没改。 ✅ 固定到具体版本标签如php:8.4-fpm-alpine配合定期主动升级。7. 改了 ini 却放在/etc/php.ini❌ 把配置文件COPY到/etc/php.ini改的设置一个都不生效还找不到原因。 ✅ 官方镜像读/usr/local/etc/php/php.ini与/usr/local/etc/php/conf.d/*.ini.ini后缀按文件名字典序加载。8. 开发环境把opcache.validate_timestamps关掉❌ 为了性能在开发环境设opcache.validate_timestamps0改了代码刷新页面毫无变化误以为挂载卷没生效开始怀疑-v路径写错。 ✅ 开发环境保持默认或设为 1生产环境再关掉并配合重启容器发布。总结现象 / 做法原因正确姿势docker run php秒退默认CMD是php -a无 TTY 时读到 EOF 即退出显式给出前台命令-it就能常驻stdin 接上了REPL 在等输入不要用交互模式当服务想跑 Webfpm 只提供 FastCGI配 nginxfastcgi_pass php:9000docker stop很慢PID 1 是 shell信号到不了服务入口脚本末尾exec $改 ini 无效配置目录认错了用/usr/local/etc/php/conf.d/*.ini生产翻车:latest是浮动标签固定版本标签容器启动即退出几乎从来不是 bug而是容器模型在如实执行你给的命令。记住三句话就够了镜像名之后的参数会覆盖默认命令容器只活在 PID 1 的生命周期里给 PID 1 一个前台常驻进程并让它收到信号。按用途选对标签再配上 nginxPHP 在 Docker 里其实非常稳定。
返回列表