尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Docker配置PHP开发环境教程
前言用 Docker 搭 PHP 环境最大的价值不是省安装步骤而是把环境本身变成可以提交到版本库的代码。换一台机器docker compose up -d之后环境完全一致不会出现我这台能跑你那台报错。但刚上手时有三类误解特别常见。第一以为拉一个php镜像就能当 Web 服务器用——实际官方php:fpm镜像里只有 PHP 解释器和 php-fpm 进程没有任何 Web 服务器浏览器访问不了php:apache镜像才自带 Apache。第二以为在容器里docker compose exec进去改php.ini就改好了——容器是临时的重建之后改动全丢任何配置都必须写进 Dockerfile 或挂载文件。第三以为 compose 里写了depends_on就等于数据库已经就绪——depends_on只保证启动顺序不保证服务可用PHP 照样可能在 MySQL 初始化完成前就发起连接。本文搭建一套 nginx php-fpm MySQL 的三容器开发环境重点讲清楚镜像选择、配置落盘、容器间寻址这三件事。命令与配置片段是静态推演的未在真实机器上执行验证镜像标签、可用版本请以 Docker Hub 上官方镜像页面为准。一、镜像怎么选官方 PHP 镜像按用途分成几个变体选错会白折腾很久镜像标签内容适用场景php:8.3-cli只有 PHP 命令行跑脚本、跑测试不提供网页服务php:8.3-fpmPHP php-fpm 进程管理器搭配独立的 nginx 容器最常见的生产形态php:8.3-apachePHP Apachemod_php线程安全单容器快速起步习惯.htaccess的项目php:8.3-fpm-alpine基于 Alpine Linux 的 fpm追求更小的镜像体积两个决策点fpm 还是 apache。追求贴近生产就用fpm加一个独立nginx容器只想少一个容器、快速看到页面用apache变体最省事。本文按前者搭。Debian 还是 Alpine。Alpine 用 musl libc镜像是小一些但编译扩展时依赖的软件包名和 Debian 不同遇到问题时网上能搜到的解法大多是 Debian 版。新手建议先用默认的 Debian 变体。标签建议固定到具体次版本如php:8.3.10-fpm而不是只用php:8.3-fpm。后者会随官方推送补丁而漂移昨天还好今天构建失败通常就是这么来的。二、用 compose 组织三个容器推荐的目录结构project/├─ docker-compose.yml├─ .env├─ docker/│ ├─ php/Dockerfile│ └─ nginx/default.conf└─ src/└─ index.phpdocker-compose.ymlservices:php:build:context: .dockerfile: docker/php/Dockerfilevolumes:- ./src:/var/www/htmlworking_dir: /var/www/htmldepends_on:mysql:condition: service_healthynginx:image: nginx:1.27-alpineports:- 8080:80volumes:- ./src:/var/www/html- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:rodepends_on:- phpmysql:image: mysql:8.0environment:MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}MYSQL_DATABASE: appMYSQL_USER: appMYSQL_PASSWORD: ${MYSQL_PASSWORD}volumes:- mysql_data:/var/lib/mysqlhealthcheck:test: [CMD, mysqladmin, ping, -h, localhost]interval: 5stimeout: 3sretries: 10volumes:mysql_data:.env与 compose 同目录不要提交到版本库MYSQL_ROOT_PASSWORDchange-me-rootMYSQL_PASSWORDchange-me-app几个要点不要写version:字段。Compose V2 规范里它已经废弃写了会打印一条警告没有实际作用。condition: service_healthy必须配合被依赖服务的healthcheck才有意义它才是等数据库真的能接受连接。只用depends_on: [mysql]属于只等启动不等就绪。数据卷mysql_data是命名卷放在文件系统里由 Docker 管理。不声明它的话MySQL 数据写在容器可写层docker compose down之后就没了。PHP 容器不需要ports。它只被 nginx 通过内部网络访问对外暴露没有意义反而扩大攻击面。只有 nginx 映射了宿主机端口示例用 8080避免和本机已有服务抢 80。docker/php/DockerfileFROM php:8.3-fpmRUN apt-get update \ apt-get install -y --no-install-recommends \libzip-dev libpng-dev libjpeg-dev libfreetype6-dev libonig-dev \ rm -rf /var/lib/apt/lists/*# gd 的 configure 参数PHP 7.4 起用 --with-freetype / --with-jpeg# 更早的版本用的是 --with-freetype-dir / --with-jpeg-dirRUN docker-php-ext-configure gd --with-freetype --with-jpeg \ docker-php-ext-install -j$(nproc) pdo_mysql mysqli gd zip# OPcache 在官方镜像里已编译好直接启用即可RUN docker-php-ext-enable opcache官方镜像提供了几个打包好的辅助脚本理解它们能省下大量时间docker-php-ext-install 扩展名...把 PHP 源码里的内置扩展编译安装会调用phpize、configure、make。部分扩展需要先装系统依赖比如zip需要libzip-dev。docker-php-ext-configure 扩展名 选项...在执行configure之前追加选项gd的 freetype/jpeg 开关就靠它。docker-php-ext-enable 扩展名启用已经编译好或镜像自带的扩展本质是往conf.d写一个 ini 文件。需要 PECL 上的第三方扩展时用pecl install 名字再docker-php-ext-enable 名字有些扩展还要先docker-php-source extract取出源码目录装完再docker-php-source delete清理。docker/nginx/default.confserver {listen 80;server_name localhost;root /var/www/html;index index.php index.html;location / {try_files $uri $uri/ /index.php?$query_string;}location ~ \.php$ {# 用 compose 里的服务名寻址不要写 127.0.0.1fastcgi_pass php:9000;fastcgi_index index.php;include fastcgi_params;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;}}fastcgi_pass php:9000是这套配置里最关键的一行。compose 会为每个服务在自定义网络里注册 DNS 名称php 就是 PHP 容器的地址。写成127.0.0.1:9000是最常见的错误——那指向的是 nginx 容器自己结果必然是 502。三、启动、验证与进入容器# 构建并后台启动首次要编译扩展会慢一些docker compose up -d --build# 看服务状态确认没有反复重启docker compose ps# 在 php 容器里执行命令确认版本与扩展docker compose exec php php -vdocker compose exec php php -mdocker compose exec php php --ini# 实时看日志排查 502 时最有用docker compose logs -f phpdocker compose logs -f nginx# 收工时停止并保留数据卷加 -v 才会连数据一起删docker compose down注意命令写法现在是docker compose空格V2 插件早期是docker-compose连字符Python 版 V1。V1 早已停止维护新环境直接用 V2 写法。写一个src/index.php验证链路是否打通?php// 适用于 PHP 8.0declare(strict_types1);header(Content-Type: text/plain; charsetutf-8);echo PHP 版本: , PHP_VERSION, PHP_EOL;echo SAPI: , PHP_SAPI, PHP_EOL;foreach ([pdo_mysql, mysqli, gd, Zend OPcache] as $ext) {printf(%-12s %s\n, $ext, extension_loaded($ext) ? 已加载 : 缺失);}浏览器访问http://localhost:8080/SAPI应该显示fpm-fcgi。如果拿到 502按这个顺序查docker compose ps看 php 容器是不是起来了、docker compose logs php看 fpm 有没有报错、最后确认fastcgi_pass写的是服务名而不是127.0.0.1。四、容器之间怎么连数据库在容器里localhost永远是容器自己不是宿主机也不是别的容器。所以 PHP 连 MySQL 的 host 必须写服务名?php// 适用于 PHP 8.0// host 用 compose 服务名 mysql端口是容器网络内的 3306$dsn mysql:hostmysql;port3306;dbnameapp;charsetutf8mb4;$options [PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES false, // 用服务端真预处理];try {$pdo new PDO($dsn, app, getenv(MYSQL_PASSWORD) ?: , $options);} catch (PDOException $e) {// 生产环境不要把异常原文直接输出给访问者error_log(数据库连接失败: . $e-getMessage());http_response_code(500);exit(服务暂不可用);}$stmt $pdo-prepare(SELECT VERSION() AS v, DATABASE() AS d);$stmt-execute();$row $stmt-fetch();echo MySQL 版本: , $row[v], PHP_EOL;echo 当前库: , $row[d], PHP_EOL;即使配了 healthcheck第一次up的时候仍可能赶上 MySQL 还在初始化。开发环境里给连接加一点重试是务实的做法?php// 适用于 PHP 8.0简单的带重试连接function connectWithRetry(string $dsn, string $user, string $pass, int $tries 10): PDO{$last null;for ($i 1; $i $tries; $i) {try {return new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION,]);} catch (PDOException $e) {$last $e;sleep(2);}}throw new RuntimeException(数据库始终不可用: . $last?-getMessage(), 0, $last);}自动加载 PHP 8 的空安全操作符?-需要 PHP 8.0 及以上在 PHP 7 上要写成$last null ? : $last-getMessage()。常见坑点❌ 拉php:8.3-fpm镜像映射 80 端口后用浏览器访问得到连接被重置✅fpm镜像里没有 Web 服务器只监听 FastCGI 端口。要么改用php:8.3-apache要么按本文加一个 nginx 容器。❌ 用docker compose exec php进去改/usr/local/etc/php/conf.d/xxx.ini重启容器后配置没了✅ 容器重建即丢失。改配置要么写进 Dockerfile 的RUN指令要么把宿主机文件挂载进conf.d。❌ nginx 里写fastcgi_pass 127.0.0.1:9000;页面 502✅ 容器内127.0.0.1是容器自身。要用 compose 服务名如fastcgi_pass php:9000;。❌ PHP 里写new PDO(mysql:hostlocalhost;...)报连接失败✅ 同理localhost是 PHP 容器自己。host 必须写成数据库服务的名字mysql。❌ 写了depends_on: [mysql]就认为数据库一定就绪结果偶发连接被拒绝✅depends_on只管启动顺序。要等就绪得给 MySQL 加healthcheck并用condition: service_healthy。❌ 不声明命名卷docker compose down之后数据全丢✅ MySQL 的数据目录必须挂到命名卷或宿主机目录上否则它只活在容器的可写层里。❌ 把数据库密码直接写在docker-compose.yml里提交进 Git✅ 用.env文件加${变量名}引用并把.env加入.gitignore生产环境的凭据应由编排平台的密钥机制注入。❌ 在 Dockerfile 里COPY . .且没有.dockerignore把vendor/、.git/全塞进构建上下文✅ 加.dockerignore排除vendor、.git、node_modules构建会明显更快也能避免把本地依赖覆盖进镜像。总结环节正确做法典型错误镜像选择fpm nginx 双容器或 apache 单容器以为 fpm 镜像自带 Web 服务器配置持久化写进 Dockerfile 或用挂载进容器手改 php.ini服务寻址用 compose 服务名php、mysql用 127.0.0.1 或 localhost启动顺序healthcheck 加condition: service_healthy只写depends_on数据持久化命名卷或宿主机目录依赖容器可写层凭据管理.env加.gitignore明文写在 compose 文件里把这套环境跑通的关键就三句话PHP 容器只提供 FastCGIWeb 服务器另起一个容器容器之间用服务名互相找localhost只指自己凡是希望留存的东西配置、数据、依赖都必须落在镜像构建指令或挂载卷上而不是容器里。抓住这三条Docker 配 PHP 环境就不再是靠运气的事了。
RELATED

相关推荐

如何快速上手 Nemotron 训练自动化:AI Agent 驱动的 nemotron-customize 技能插件完整实战

如何快速上手 Nemotron 训练自动化:AI Agent 驱动的 nemotron-customize 技能插件完整实战

如何快速上手 Nemotron 训练自动化:AI Agent 驱动的 nemotron-customize 技能插件完整实战 【免费下载链接】Nemotron Developer Asset Hub for NVIDIA Nemotron — A one-stop resource for training recipes, usage cookbooks, datasets, and full end-to-end ref…

📅 2026/10/8 23:37:01
终极解析:BlueHammer 的 VSS 卷影复制技巧——FreezeVSS 如何用 HarddiskVolumeShadowCopy 冻结 Defender

终极解析:BlueHammer 的 VSS 卷影复制技巧——FreezeVSS 如何用 HarddiskVolumeShadowCopy 冻结 Defender

终极解析:BlueHammer 的 VSS 卷影复制技巧——FreezeVSS 如何用 HarddiskVolumeShadowCopy 冻结 Defender 【免费下载链接】BlueHammer Repository hosting the bluehammer vulnerability 项目地址: https://gitcode.com/gh_mirrors/bl/BlueHammer &#x1f…

📅 2026/10/8 23:37:01
VS Code 设置 JSON 文件怎么改到 TaoToken:统一 Key 与 API 通道配置大纲

VS Code 设置 JSON 文件怎么改到 TaoToken:统一 Key 与 API 通道配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/8 23:37:01
MORE NEWS

更多资讯

📰

毕设实战:MHK在线学习小程序全栈链路拆解与避坑指南

简介:面向微信小程序毕业设计的MHK在线学习小程序项目包,基于微信开发者工具JavaMySQL实现,采用前后端分离结构,覆盖学生与管理员双角色完整闭环。学生端支持注册登录、考试资讯浏览与详情查看,并内置听力训练、阅读训…

📰

飞书CLI开源后,Claude Code办公自动化实战大纲:从Base URL改到TaoToken开始

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Claude Fable 5 正式发布,TaoToken 统一 Key 接入 Anthropic 新模型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

headcount 16部门选型完全指南:CFO、CTO、CISO等角色该装哪些技能

headcount 16部门选型完全指南:CFO、CTO、CISO等角色该装哪些技能 【免费下载链接】headcount An agent organization structured as a company — 15 departments, 125 skills, each independently installable, citing the standards and regulators that settle …

📰

Hermes Desktop多Profile多Agent协作指南:多主机AI工作流如何搭建

Hermes Desktop多Profile多Agent协作指南:多主机AI工作流如何搭建 【免费下载链接】hermes-desktop The safest, simplest way to manage Hermes from your Mac. Pure SSH. No gateways, no exposed ports, no browser layer. 项目地址: https://gitcode.com/gh_m…

📰

东莞高导热低膨胀液态芯片封装胶制造厂家企业全景分析,晋咸新材料资质齐全

核心原理科普:芯片封装胶如何破解大功率算力时代的散热与应力难题在AI算力爆发、服务器芯片功率持续攀升的当下,芯片封装材料正成为制约设备稳定性的关键一环。简单来说,芯片工作时产生的热量需要通过封装胶快速导出,同时封装胶还…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬