Docker 多阶段构建实战:镜像体积减少 80% 的完整写法

12 ℃ 0 条
内容摘要单阶段 Dockerfile 会把编译工具链和依赖打进镜像,体积常超 1GB。本文以 Composer 项目为例讲清多阶段构建原理,给出可复用的两阶段 Dockerfile 代码,说明合并 RUN、清理缓存和 .dockerignore 的要点,把镜像压到 200MB 内。
Docker 多阶段构建实战:镜像体积减少 80% 的完整写法

Docker 多阶段构建分层示意图

一个只装 PHP 运行时的镜像本该不到 200MB,实际却常常超过 1GB,原因多半是编译工具链与构建缓存被一层层留在了镜像里。Docker 多阶段构建允许在同一个 Dockerfile 里写多个 FROM,只有最后一个阶段进入最终镜像,前面阶段只负责产出文件。

一、单阶段写法为什么把镜像体积撑大

1. 构建依赖和运行依赖混在一起

编译 PHP 扩展需要 gcc、make、libssl-dev,运行时完全用不到。而 Docker 镜像分层只增不减:同一层里装了再删,体积依然算在这一层。

FROM php:8.2-fpm
RUN apt-get update && apt-get install -y gcc make libssl-dev

仅这一层就多出约 400MB 编译工具,后面执行 apt-get purge,体积也不会回落。

2. Composer 缓存与 vendor 目录重复

COPY . /app
RUN composer install --no-dev

composer 的下载缓存默认在 ~/.composer/cache 且不自动清理;COPY . 还会把本地 vendor 一起带进去,造成两份依赖,单是 vendor 就可能占 60 到 120MB。

二、多阶段构建的完整写法

1. 用 as 命名阶段,只 COPY 产物

思路是把构建和运行彻底拆开,运行阶段只保留运行时和产物。

# 构建阶段:只产出文件
FROM composer:2 AS builder
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist

# 运行阶段:最终镜像只基于它
FROM php:8.2-fpm
WORKDIR /app
COPY --from=builder /app/vendor ./vendor
COPY . .
CMD ["php-fpm"]

COPY --from=builder 只搬运指定目录,builder 阶段的层不会进入最终镜像。

2. 合并 RUN 并清理包管理器缓存

apt 与 composer 的缓存必须在同一层内删除才有效,要用 && 串在一条 RUN 里,分开写会让前一层的缓存永久残留。

RUN apt-get update \
    && apt-get install -y --no-install-recommends libssl3 \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

三、上线前的校验与常见坑

1. 用 docker history 定位异常大层

docker history --no-trunc --human app:latest | head -20

按 SIZE 从大到小看,优先削掉占比最高的那一层,通常就是漏清理的 apt 或 composer。

2. .dockerignore 与构建缓存的取舍

构建缓存命中依赖文件内容与拷贝顺序。把 composer.json 与 composer.lock 单独先 COPY 再 install,只要依赖清单不变,改业务代码时这一步依然命中。同时必须写 .dockerignore 排除无谓目录,否则 COPY . . 会把它们带进镜像并打穿缓存。

.git
node_modules
vendor
.env

关键配置提示

  • 构建阶段直接用官方 composer:2 镜像,--prefer-dist 走压缩包比 --prefer-source 快很多。
  • 改完 Dockerfile 后用 --no-cache 与默认缓存各构建一次,对比体积差异,确认清理确实生效。

上一篇:没有了

下一篇:没有了

发表评论 (已有0条评论)

快来评论,快来抢沙发吧~