0Pricing
PHP Academy · 课时

多阶段构建与优化

缩小镜像,并分离构建环境与运行环境

多阶段构建与优化 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。

为什么使用多阶段构建

单阶段镜像会将 Composer、构建依赖、开发头文件和您的 tests/ 文件夹一起带入生产环境。多阶段构建允许您在包含完整工具的构建阶段中编译和安装,然后只将已完成的构建产物复制到精简的运行时阶段。

结果是:镜像更小、攻击面更小、拉取速度更快,并且不会将编译器部署到生产环境。

命名阶段

每个 FROM ... AS name 都会开始一个新的阶段。后续阶段可以使用 COPY --from=name 从前面的阶段复制文件。只有最终阶段会成为您的镜像;中间阶段会被丢弃,但仍会被缓存。

# Stage 1: dependencies
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --ignore-platform-reqs

# Stage 2: runtime
FROM php:8.3-fpm-alpine AS runtime
WORKDIR /app
COPY --from=vendor /app/vendor ./vendor
COPY . .

分离构建依赖

编译扩展需要 autoconf、gcc 和开发头文件,但这些都不应存在于运行时环境中。请在构建阶段使用安装器,然后将已编译的 .so 文件和匹配的 conf.d ini 文件复制到干净的运行时阶段。

FROM php:8.3-fpm-alpine AS ext-builder
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
    install-php-extensions redis igbinary opcache intl

FROM php:8.3-fpm-alpine AS runtime
# Copy compiled extensions + their enable configs
COPY --from=ext-builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=ext-builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/

层缓存顺序

Docker 会从上到下缓存各层;某一层发生变化后,其下方的所有层都会失效。请按照最少到最频繁变化的顺序排列:

  • 基础镜像 + 扩展(很少变化)
  • composer.lock + 安装步骤(偶尔变化)
  • 应用源代码(每次提交都会变化)
  • 自动加载信息转储(每次提交都会变化)

这意味着仅修改代码时,可以完整复用缓存的依赖包层。

# BAD: copying all source before composer install
# busts the vendor layer on every code change
COPY . .
RUN composer install

# GOOD: lock first, then source
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-autoloader
COPY . .
RUN composer dump-autoload --optimize

BuildKit 缓存挂载

使用 BuildKit(DOCKER_BUILDKIT=1)时,您可以挂载一个能够跨构建持续存在的缓存,而不会将其写入镜像。它非常适合 Composer 的全局缓存,可以让重复构建跳过重新下载软件包的步骤。

# syntax=docker/dockerfile:1
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/composer-cache \
    COMPOSER_CACHE_DIR=/tmp/composer-cache \
    composer install --no-dev --prefer-dist

测量镜像大小

请检查各层的大小分布,以找出臃肿部分。docker history 会显示每条指令增加的大小;dive 等工具则可以显示浪费的空间。目标是构建一个不包含编译器、Composer 和开发依赖的运行时阶段。

# Compare sizes
docker images myapp

# Per-layer contribution
docker history --no-trunc --format '{{.Size}}\t{{.CreatedBy}}' myapp:latest

# Deep inspection of wasted bytes
dive myapp:latest

精简最终阶段

运行时阶段不应包含 Composer、扩展安装脚本或您的测试套件。请从构建阶段复制 vendor 和源代码;不要在最终阶段运行 composer。如果必须在那里运行安装器,请在使用后将其删除。

FROM php:8.3-fpm-alpine AS runtime
WORKDIR /app

# bring extensions + vendor in from builders — no Composer here
COPY --from=ext-builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=ext-builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY --from=vendor /app/vendor ./vendor
COPY . .

USER www-data
CMD ["php-fpm"]

Distroless / Scratch 的限制

PHP 无法在真正空白的 scratch 上运行,因为它需要 libc 和共享库。实际可行的最低配置是 alpine(musl)或采用 distroless 风格的精简 Debian。Alpine 的体积最小,但请留意依赖 glibc 的原生库;如果使用 NSS/ICU 时遇到段错误,请回退到 php:8.3-fpm-bookworm。

# Smallest practical PHP runtime
FROM php:8.3-fpm-alpine

# If musl causes native-lib issues (e.g., some ICU edge cases),
# the glibc Debian slim variant is the safe fallback:
# FROM php:8.3-fpm-bookworm

指定构建阶段

一个 Dockerfile 可以通过 --target 同时服务于开发环境和生产环境。在运行时阶段之上添加一个 dev 阶段,重新加入 Composer 开发依赖和 Xdebug;生产环境使用 --target=runtime 构建,本地则使用 --target=dev。

FROM runtime AS dev
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
    install-php-extensions xdebug @composer
USER root
RUN composer install   # includes dev deps

# Build prod:  docker build --target runtime -t app:prod .
# Build dev:   docker build --target dev     -t app:dev  .

分析层大小

建立一个简单的心智模型,可以帮助您预测缓存行为。这个 CLI 片段模拟了经典的无上限层增长错误与设置上限的做法,说明为什么必须将清理操作合并到同一个 RUN 中。

<?php
// Simulate layer sizes (MB) for two strategies
$installSteps = [120, 8, 8, 8];

// Separate RUN per step keeps temp files in layers
$separate = array_sum($installSteps);

// Single RUN with cleanup removes temp files before commit
$combined = max($installSteps); // peak, then cleaned

echo "Separate layers total: {$separate} MB\n";
echo "Combined+cleanup:       {$combined} MB\n";
echo 'Saved: ' . ($separate - $combined) . " MB\n";
?>

在同一个 RUN 中合并并清理

每个 RUN 都是一层;在后续层中删除文件并不会缩小镜像,因为之前的层仍然保存着这些字节。请在同一个 RUN 中完成安装、使用和清理,这样临时文件就不会被提交到镜像中。

RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \
    pecl install redis && \
    docker-php-ext-enable redis && \
    apk del .build-deps && \
    rm -rf /tmp/pear /var/cache/apk/*

快速检查

为什么必须在安装构建依赖的同一个 RUN 中将其删除?

回顾

多阶段构建可以将编译器和开发依赖排除在生产环境之外。您学会了:为阶段命名并使用 COPY --from 复制构建产物;按照从不易变化到最易变化的顺序排列层以利用缓存;为 Composer 使用 BuildKit 缓存挂载;使用 docker history/dive 测量大小;有意识地选择 alpine 或 glibc;指定开发和生产阶段;并在同一个 RUN 中合并安装与清理。

常见问题解答

「多阶段构建与优化」课时是免费的吗?

是的 — 「多阶段构建与优化」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。

「多阶段构建与优化」这节课中我会学到什么?

缩小镜像,并分离构建环境与运行环境 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 PHP Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「多阶段构建与优化」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 PHP Academy 课中编写并运行代码吗?

能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 将 PHP 应用容器化
  2. 多阶段构建与优化
  3. 使用 Docker Compose 构建本地技术栈
  4. 使用 GitHub Actions 实现 CI/CD
← 返回 PHP Academy