VPS优惠推送
分享好玩教程

在 Docker 里升级 PHP:换镜像,不是 restart

在主机上装 PHP,升级就是一条 apt upgrade。但同样的思路搬到 Docker 里,会撞上两个坑:

  • docker restart —— 只重启进程,镜像还是旧的,等于没升;
  • 进容器里 apt upgrade —— 看起来升了,下次重建容器就全丢。

因为 Docker 里的 PHP 版本来自镜像,不来自系统包管理器。所以升级逻辑只有一句:换镜像 → 重建容器。

下面用最常见的 nginx + php-fpm + mysql 三件套走一遍完整流程,最后再补一句「只有一个 php:fpm 容器」的简单情况。

1、先理解为什么:镜像不可变,容器只是它的一个实例

升级的本质:拿到装着新 PHP 的镜像,再用它重建容器
  • 镜像是"快照",里面的 PHP 版本是固定的;
  • 容器是镜像跑起来的一个实例;
  • 你在容器里改任何东西(装扩展、改 php.ini、apt upgrade),都只存在于这个容器里,镜像没变 —— 重建时就消失了。

所以"升级 PHP"这件事,本质是:拿到一个装着新版 PHP 的镜像 → 用它重建容器。旧容器里的数据(数据库、上传的文件)靠 volume 保留,不会丢。

2、第一步永远是"看":别信 tag,看容器里真实的版本

# 看运行中的容器
docker compose ps

# 看容器里真实的 PHP 版本(这一步最关键)
docker compose exec php-fpm php -v

为什么不能只看 tag?因为 tag 有两种写法:

tag 写法 含义 pull 之后会怎样
php:8.4-fpm 浮动 tag,跟着 8.4 这条线走 ✅ 拿到最新的 8.4.x
php:8.4.26-fpm 钉死版本 ❌ 永远还是 8.4.26

所以如果你在 compose 里写的是钉死版本,docker compose pull 什么也不会变 —— 必须先改 tag。

想批量看一下所有容器的 PHP 版本,可以用这条:

for c in $(docker ps --format '{{.Names}}'); do
  echo -n "$c → "
  docker exec "$c" php -v 2>/dev/null | head -1 || echo "无 PHP"
done

3、三件套怎么升(主线)

典型的 compose 长这样(节选,只保留关键部分):

services:
  php-fpm:
    image: php:8.4-fpm          # ← 或者 build: ./php
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
    restart: unless-stopped

  nginx:
    image: nginx:1.27-alpine
    volumes:
      - ./www:/var/www/html
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on: [php-fpm]

  mysql:
    image: mysql:8.4
    volumes:
      - db:/var/lib/mysql

这个结构里,只有 php-fpm 需要动:nginx 只是把请求转发给 php-fpm,mysql 和 PHP 版本无关。

compose 里写 image 还是 build,决定你用哪条升级路径

情况 A:用的是官方镜像(image: php:8.4-fpm)

# 1. 拉新镜像(浮动 tag 会拿到最新的 8.4.x)
docker compose pull php-fpm

# 2. 重建容器(注意是 up,不是 restart)
docker compose up -d php-fpm

# 3. 验证
docker compose exec php-fpm php -v

如果没有额外装 PHP 扩展,到这里就结束了。这也是为什么我一直建议:能不加扩展就别加——纯官方镜像的升级最省事。

情况 B:用 Dockerfile 自建(装了扩展)

大多数人会需要一些扩展(mysqli、gd、redis、zip……),于是有个自己的 Dockerfile:

FROM php:8.4-fpm

RUN apt-get update && apt-get install -y libzip-dev libpng-dev 
 && docker-php-ext-install pdo_mysql mysqli gd zip 
 && pecl install redis && docker-php-ext-enable redis

COPY php.ini /usr/local/etc/php/php.ini

这时候升级要重新构建,并且带上 --pull —— 否则 Docker 会用本地的旧 base 镜像:

# 关键:--pull 会去拉最新的 php:8.4-fpm
docker compose build --pull php-fpm

# 重建
docker compose up -d php-fpm

# 验证(版本 + 扩展都在)
docker compose exec php-fpm php -v
docker compose exec php-fpm php -m | head -20

注意 --pull 这一步:不加它,build 很可能复用缓存,你以为升级了,其实用的还是旧的 php:8.4-fpm。

一次完整的升级:查版本 → build --pull → up -d → exec 验证

升级后检查

  1. docker compose exec php-fpm php -v 版本已变;
  2. docker compose ps 容器状态是 Up;
  3. 打开你的网站/接口,确认能正常响应(尤其是有扩展的项目);
  4. 看一下 nginx 的错误日志里有没有 fpm 连接失败:docker compose logs --tail=50 nginx。

回滚:留一个旧 tag 就能秒回

升级前把旧的 tag 记下来(或用 digest),万一新版本有兼容问题:

# 把 compose 里的 tag 改回旧版本,然后
docker compose up -d php-fpm

因为旧镜像还在本地,重建是秒级的。建议升级前先 docker image ls | grep php 看一眼旧 tag 还在不在。

4、只有一个 php:fpm 容器的情况(简单说一下)

如果你没有用 compose,就是一个 docker run 起的 php:fpm,逻辑一样,但命令要连挂载一起重来:

docker pull php:8.4-fpm
docker stop php-fpm-old
docker run -d --name php-fpm --network app 
  -v /data/www:/var/www/html 
  -v /data/php/php.ini:/usr/local/etc/php/php.ini 
  php:8.4-fpm

⚠️ 这种方式的坑在于:原来的挂载、网络、环境变量都得手动再写一遍,很容易漏。所以更推荐把它迁到 compose——写一次 docker-compose.yml,以后升级就是两条命令的事。

5、四个最常见的坑

四个最常见的坑,踩一个就等于「以为升了,其实没升」
坑 为什么错 正确做法
docker compose restart 只重启进程,镜像没换 pull / build 之后用 up -d
进容器里 apt upgrade 改动只在容器里,重建就丢 写进 Dockerfile,重建镜像
只改 tag 不 --pull build 会复用旧 base 缓存 docker compose build --pull
直接改容器里的 php.ini 重建后还原 用 Dockerfile COPY 或 volume 挂载

另外,大版本升级(比如 8.3 → 8.4)时,扩展可能不兼容:有的扩展版本跟不上,有的编译参数变了。升级时留意 build 的输出,看到编译错误就先把那个扩展注释掉,单独排查。

6、自动更新(可选,谨慎)

社区的 watchtower 可以自动帮你 pull 新镜像并重建容器。图省事可以,但要想清楚:

  • 它会在你没盯着的时候重启容器 —— 生产站可能刚好在忙;
  • 新版本不兼容时,它不会帮你回滚;
  • 更稳的做法是:收到安全公告 → 手动升 → 验证 → 观察一两天。

7、一句话记住

升级 = 换镜像 + 重建容器;回滚 = 换回旧 tag;验证 = 进容器里看 php -v。

下次遇到类似 PHP2Shell 这种"今晚必须升"的安全公告,Docker 用户照这个顺序走一遍就行,不比主机用户麻烦多少 —— 前提是别用 restart。

本文示例基于 nginx + php-fpm + mysql 的典型 compose 结构;具体镜像 tag 以各镜像官方发布页为准。

赞(0)
未经允许不得转载:F12笔记 » 在 Docker 里升级 PHP:换镜像,不是 restart

评论 抢沙发