必须在构建阶段执行,不能放到容器启动时,因为CMD/ENTRYPOINT中运行composer install会导致每次启动都重拉包、破坏缓存、网络失败即退出、扩展缺失无法修复,且违反“一次构建、多次运行”原则。

composer install 必须在构建阶段执行,不能放到容器启动时。这是硬性约束,不是风格偏好。
为什么不能在 CMD/ENTRYPOINT 里跑 composer install
常见错误是把依赖安装写进启动命令,比如:
CMD ["sh", "-c", "composer install --no-dev && php -S 0.0.0.0:8000"]
这会直接导致:
- 每次
docker run都重走一遍网络拉包流程,网络抖动就失败 -
vendor/不进镜像层,后续任何COPY或RUN变更都会让 composer 步骤失效(无法命中缓存) - 生产环境通常禁外网,
Could not fetch packages报错后容器直接退出 - PHP 扩展缺失(如
ext-zip)要到启动时才暴露,但此时镜像已无法修复
docker-php-ext-install 失败前先装系统库
PHP 扩展编译失败,90% 是因为缺底层开发包,docker-php-ext-install 本身不解决依赖。
例如装 zip 扩展,必须提前装 libzip-dev:
RUN apt-get update && apt-get install -y libzip-dev \
&& docker-php-ext-install zip \
&& rm -rf /var/lib/apt/lists/*
其他常见对应关系:
-
pdo_mysql→default-libmysqlclient-dev(Debian/Ubuntu)或mysql-client+mysql-devel(Alpine) -
gd→libfreetype6-dev,libjpeg-dev,libpng-dev -
sodium→libsodium-dev(再加docker-php-ext-install sodium)
Dockerfile 分层顺序直接影响构建速度和复现性
关键不是“能不能装上”,而是“哪一层装、装什么、怎么清理”。典型正确顺序:
FROM php:8.1-cli-slim
# 1. 装系统依赖(扩展编译所需)
RUN apt-get update && apt-get install -y libzip-dev default-libmysqlclient-dev \
&& rm -rf /var/lib/apt/lists/*
<h1>2. 装 PHP 扩展(基于上一步的系统库)</h1><p>RUN docker-php-ext-install zip pdo_mysql</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill12074" title="Docker Hardening"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20dee630206.jpg" alt="Docker Hardening" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill12074" title="Docker Hardening" class="overflowclass">Docker Hardening</a>
<p class="overflowclass">专业的Docker安全配置生成器,符合CIS Benchmark v1.8.0标准。</p>
</div>
<a rel="nofollow" href="/xiazai/skill12074" title="Docker Hardening" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h1>3. 复制锁定文件,安装 Composer 依赖(利用缓存)</h1><p>COPY composer.lock composer.json ./
RUN curl -sS <a href="https://www.php.cn/link/e910517884e11c8a741c3b1da823f47e">https://www.php.cn/link/e910517884e11c8a741c3b1da823f47e</a> | php -- --install-dir=/usr/local/bin --filename=composer \
&& composer install --no-dev --optimize-autoloader</p><h1>4. 最后复制源码(变动最频繁,放最后)</h1><p>COPY . /app
WORKDIR /app</p>
注意点:
-
composer.lock和composer.json放一起 COPY,确保缓存能复用 - 用
--no-dev和--optimize-autoloader减少生产镜像体积和加载开销 - 不要用
ADD解压或拉远程包,统一用COPY+ 显式命令
多阶段构建适合含前端构建的 PHP 项目
如果项目含 Vue/React 前端,且需要 npm install + npm run build,别把 Node 环境塞进最终镜像。
用多阶段构建分离关注点:
FROM node:18-alpine AS frontend-builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build <p>FROM php:8.1-apache-slim</p><h1>复制前端产物,不带 node_modules</h1><p>COPY --from=frontend-builder /app/dist /var/www/html/dist</p><h1>复制后端代码和 vendor</h1><p>COPY --from=0 /app/vendor /var/www/html/vendor COPY --from=0 /app/src /var/www/html/src</p>
这样最终镜像里没有 Node、npm、webpack,只有 PHP 运行时和静态资源,体积小、攻击面小、启动快。
构建阶段的顺序和分层粒度,比语法细节更重要。一个没装 libzip-dev 的 docker-php-ext-install zip 命令,无论写得多漂亮,都会卡死。










