1. 从零到一理解 Craft CMS Docker 镜像的定位与价值如果你正在或计划使用 Craft CMS 来构建内容驱动的网站并且你的技术栈里包含了 Docker那么你很可能已经听说过或者正在寻找craftcms/docker这个项目。简单来说这是由 Craft CMS 官方维护的一套 Docker 镜像旨在为开发者提供一个标准化、开箱即用的容器化部署和开发起点。它的核心价值在于将 Craft CMS 运行所需的环境PHP、Nginx、必要的扩展以及最佳实践如非 root 用户运行打包成轻量级的镜像让你可以跳过繁琐的环境配置直接聚焦于应用开发本身。这套镜像主要服务于两类人群一是希望快速搭建本地开发环境实现团队环境统一的开发者二是计划将 Craft CMS 应用部署到 Docker 化生产环境如 AWS ECS、Kubernetes的运维或全栈工程师。对于新手它降低了入门门槛对于老手它提供了可靠、可复现的基础设施层。不过需要特别注意的是根据项目仓库的说明这个仓库及其镜像即将被归档新的镜像维护工作已经迁移到了 craftcms/image 仓库。这意味着虽然现有镜像短期内仍可使用但未来的更新、新特性以及安全补丁将集中在新仓库。因此对于新项目我强烈建议你直接关注新的craftcms/image仓库。但理解这个“旧”项目的设计思路对于你掌握 Craft CMS 的容器化精髓依然至关重要因为很多核心理念和配置方法是一脉相承的。2. 镜像家族全解析php-fpm, nginx 与 cli 的职责与选型这套镜像并非一个单一的“万能”镜像而是根据不同的职责进行了精细的划分主要分为三个类型php-fpm、nginx和cli。这种设计体现了现代应用部署中“单一职责”和“服务分离”的原则。理解每个镜像的用途是你正确使用它们的第一步。2.1 php-fpm 镜像纯粹的应用处理器craftcms/php-fpm是这个镜像家族的基础。它只包含运行 Craft CMS 所需的 PHP-FPM 环境及核心扩展如 GD、Intl、PDO 等不包含 Web 服务器。这意味着你需要“自带服务器”通常是在另一个容器中运行 Nginx 或 Apache并通过 FastCGI 协议与这个 php-fpm 容器通信。为什么选择它这种分离架构在生产环境中非常常见尤其是在需要水平扩展的场景下。你可以独立地扩展 PHP-FPM 容器实例的数量而前端的 Nginx 容器可以作为负载均衡器或静态资源服务器。它提供了最大的灵活性和可扩展性。例如在 Kubernetes 中你可以分别部署 php-fpm 的 Deployment 和 Nginx 的 Deployment并通过 Service 进行连接。版本选择策略镜像标签遵循craftcms/php-fpm:php版本的格式例如craftcms/php-fpm:8.2。官方镜像的 PHP 版本支持策略与 PHP 官方支持周期严格对齐。一个至关重要的实践原则是永远不要使用已标记为 EOL生命周期结束的 PHP 版本无论是开发还是生产环境。例如PHP 7.4 及更早的版本已经停止安全更新使用它们会带来严重的安全风险。你应该始终选择处于活跃支持期的最新稳定版本目前以常见实践而言是 PHP 8.2 或 8.3。表格中那些 EOL 的版本列表对你而言应该只是一个“历史记录”或“避坑指南”而不是可选项。2.2 nginx 镜像一体化的便捷选择craftcms/nginx镜像可以理解为php-fpm镜像的超集。它在内部集成了一个预配置好的 Nginx 服务器并与 PHP-FPM 进程通过 Unix Socket 或 TCP 在容器内部进行通信。这个镜像对外只暴露一个 HTTP 端口默认是 8080。为什么选择它这是为了极致的简便性特别适合快速原型开发、小型项目或对架构复杂度敏感的场景。你只需要运行这一个容器它就同时具备了 Web 服务器和应用运行环境。在本地开发中使用它可以一键启动一个完整的 Craft CMS 运行环境无需额外编排 Nginx 配置。它简化了 Docker Compose 的配置让开发者能更快地进入编码状态。需要注意的细节虽然它内置了 Nginx但其配置是针对 Craft CMS 优化过的通用配置。如果你的项目有特殊的 Nginx 需求例如复杂的重写规则、自定义的client_max_body_size等你可能需要基于此镜像创建自己的 Dockerfile在构建时覆盖默认的 Nginx 配置文件。不过对于绝大多数标准 Craft CMS 项目这个默认配置已经足够优秀。2.3 cli 镜像后台任务的执行者craftcms/cli镜像是一个特殊的成员。它不包含 PHP-FPM也不暴露任何网络端口。它的唯一目的就是执行命令行任务。它的核心职责是什么在 Craft CMS 中有许多重要的后台作业需要定期或异步执行例如队列任务处理使用php craft queue/listen监听并处理异步队列如发送邮件、生成图片变体。运行迁移在部署新版本时执行php craft migrate/all来更新数据库结构。执行自定义控制台命令你或插件开发的任何craft命令。生成静态资源如果前端构建依赖 Node 环境则需另行处理。在生产环境中我们通常不会在处理 Web 请求的php-fpm容器中运行这些长时间阻塞的命令。相反我们会单独运行一个或多个cli容器实例来专门负责这些后台任务实现职责分离避免影响网站响应速度。开发与生产变体-dev 后缀的奥秘每个镜像类型都提供了“生产”和“开发”两种变体通过-dev后缀区分例如craftcms/nginx:8.2和craftcms/nginx:8.2-dev。生产变体无-dev追求极致的精简和安全。移除了所有仅用于开发调试的组件主要是 Xdebug镜像体积更小潜在的攻击面也更少。开发变体带-dev除了包含生产变体的所有功能外还预装了Xdebug和数据库客户端工具如mysql-client和postgresql-client。Xdebug 用于代码调试、性能分析和生成代码覆盖率报告是开发阶段的利器。数据库客户端工具则允许你在容器内直接执行mysqldump或pg_dump等命令这对于通过 Craft 控制面板进行数据库备份功能是必需的。重要提示你完全可以在本地开发中使用生产变体镜像这没有任何技术障碍。-dev镜像只是提供了便利。反之绝对不要将-dev镜像用于生产环境。Xdebug 会显著降低 PHP 性能并可能带来安全风险。3. 实战指南从 Dockerfile 编写到本地环境启动理解了镜像的构成接下来我们进入实战环节。我将带你一步步完成从编写自定义 Dockerfile 到使用 Docker Compose 启动一套完整本地开发环境的全过程。3.1 构建自定义应用镜像使用多阶段构建直接使用基础镜像运行容器虽然可以但最佳实践是将你的应用代码“烘焙”进一个自定义镜像中。这确保了环境的一致性。这里的关键技术是Docker 多阶段构建。让我们拆解一个标准的 Dockerfile# 第一阶段依赖安装层 FROM composer:2 as vendor COPY composer.json composer.json COPY composer.lock composer.lock RUN composer install --ignore-platform-reqs --no-interaction --prefer-dist # 第二阶段应用运行层 FROM craftcms/php-fpm:8.2 # 从上一阶段拷贝已安装的依赖 COPY --chownwww-data:www-data --fromvendor /app/vendor/ /app/vendor/ # 拷贝当前目录所有应用代码 COPY --chownwww-data:www-data . .为什么要这么做分离关注点第一阶段使用纯净的composer镜像唯一任务就是执行composer install。这利用了构建缓存只要composer.json和composer.lock没变这一层就不会重建大大加快了构建速度。保持最终镜像精简最终镜像第二阶段基于craftcms/php-fpm只包含运行环境和你自己的代码及依赖。它不会包含 Composer 本身这个构建工具减少了镜像体积和潜在漏洞。权限管理注意COPY指令中的--chownwww-data:www-data。这是因为基础镜像默认以非 root 用户www-data运行。在构建阶段就将文件所有权设置为www-data可以避免容器运行时出现权限错误例如Craft 需要向storage/目录写入日志和缓存。如果需要数据库备份工具怎么办如前所述生产变体镜像默认不包含mysql-client等工具。如果你的生产环境需要通过 Craft 控制面板备份数据库你需要在 Dockerfile 中手动安装。这时需要临时切换到root用户FROM craftcms/nginx:8.2 # 临时切换为 root 以安装系统包 USER root RUN apk add --no-cache mysql-client postgresql-client # 安装完成后必须切换回 www-data 用户 USER www-data COPY --chownwww-data:www-data --fromvendor /app/vendor/ /app/vendor/ COPY --chownwww-data:www-data . .这里使用apk add是因为基础镜像是基于 Alpine Linux。--no-cache选项是为了不缓存索引文件让镜像更小。3.2 使用 Docker Compose 编排本地开发环境对于本地开发手动管理多个容器及其网络、卷是非常低效的。Docker Compose 是解决这个问题的标准工具。下面是一个功能齐全的docker-compose.yml示例它搭建了一个包含 Web 服务器、数据库、缓存和队列处理器的完整环境。version: 3.8 services: web: image: craftcms/nginx:8.2-dev # 使用开发镜像带Xdebug ports: - 8080:8080 # 将容器8080端口映射到本地8080端口 env_file: .env # 加载Craft CMS的.env文件 environment: XDEBUG_CONFIG: client_hosthost.docker.internal # 配置Xdebug连接到宿主机 PHP_IDE_CONFIG: serverNameDockerCraft # 可选用于IDE配置 depends_on: postgres: condition: service_healthy # 等待数据库健康检查通过 redis: condition: service_healthy # 等待Redis健康检查通过 volumes: - .:/app # 将当前项目目录挂载到容器/app实现代码实时同步 - ./docker/nginx/default.conf:/etc/nginx/http.d/default.conf:ro # 可选挂载自定义Nginx配置 console: image: craftcms/cli:8.2-dev env_file: .env environment: XDEBUG_CONFIG: client_hosthost.docker.internal depends_on: postgres: condition: service_healthy redis: condition: service_healthy volumes: - .:/app command: php craft queue/listen # 容器启动后默认执行队列监听命令 postgres: image: postgres:15-alpine # 建议使用较新的版本 ports: - 5432:5432 # 暴露端口方便用本地工具如TablePlus连接 environment: POSTGRES_DB: craftcms_dev POSTGRES_USER: craft POSTGRES_PASSWORD: a_strong_password_here # 务必修改 volumes: - postgres_data:/var/lib/postgresql/data # 数据持久化卷 healthcheck: # 健康检查确保服务就绪后web服务再启动 test: [CMD-SHELL, pg_isready -U craft -d craftcms_dev] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine ports: - 6379:6379 volumes: - redis_data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 5s timeout: 3s retries: 5 volumes: postgres_data: redis_data:关键配置解读与实操心得depends_on与condition: service_healthy这是 Compose 的“服务依赖”和“健康检查”功能。它确保web和console服务只有在postgres和redis服务通过健康检查即真正准备好接受连接后才会启动。这比简单的depends_on更可靠避免了应用启动时因数据库未就绪而报连接错误。卷挂载.:/app这是开发模式的核心。它将你本地项目目录实时同步到容器的/app目录。你在本地 IDE 里修改代码容器内立即生效无需重新构建镜像。注意对于生产构建你应该使用COPY指令将代码复制进镜像而不是挂载卷。Xdebug 配置XDEBUG_CONFIGclient_hosthost.docker.internal是让容器内的 Xdebug 将调试信息发送回宿主机 IDE 的关键。host.docker.internal是 Docker Desktop 为宿主机提供的特殊域名。在 Linux 原生 Docker 环境中你可能需要使用宿主机的真实 IP 地址。数据库密码与数据持久化示例中的密码是占位符你必须修改为一个强密码。使用命名卷postgres_data,redis_data可以确保数据库数据在容器删除后依然保留。console服务这个服务专门运行队列处理器。在生产部署中你可能会将其作为一个独立的服务或任务在 ECS/Kubernetes 中运行多个副本以提高处理能力。启动这个环境只需一行命令docker-compose up -d。之后你就可以在浏览器中访问http://localhost:8080来安装和运行你的 Craft CMS 项目了。4. 高级定制与性能调优基础环境跑起来之后你可能会遇到需要定制 PHP 扩展、调整 PHP 配置以适应特定项目需求的情况。官方镜像提供了清晰的扩展路径。4.1 安装额外的 PHP 扩展假设你的项目需要sockets扩展来支持某些网络功能。你需要在自定义的 Dockerfile 中安装它。由于基础镜像基于官方的php:version-fpm-alpine你可以使用其内置的docker-php-ext-install脚本来安装核心扩展。FROM craftcms/php-fpm:8.2 # 必须切换到 root 用户来安装系统包和扩展 USER root # 安装系统依赖如果需要。例如某些扩展需要特定的库。 # RUN apk add --no-cache some-dev-library # 安装 PHP 扩展 RUN docker-php-ext-install sockets # 安装 PECL 扩展例如 redis # RUN pecl install redis docker-php-ext-enable redis # 重要安装完成后切换回非特权用户 USER www-data COPY --chownwww-data:www-data --fromvendor /app/vendor/ /app/vendor/ COPY --chownwww-data:www-data . .注意事项在安装扩展前务必查阅该扩展的文档看是否需要先安装 Alpine 的系统包通常是*-dev包。安装完成后必须记得切换回USER www-data这是容器安全运行的基本要求。4.2 灵活调整 PHP 运行时配置Craft CMS 官方镜像的一个非常贴心的特性是它允许你通过环境变量来覆盖一系列关键的 PHP 配置。这比手动编写php.ini文件要灵活和“容器化”得多。在你的docker-compose.yml或容器运行命令中可以直接设置services: web: image: craftcms/nginx:8.2-dev environment: PHP_MEMORY_LIMIT: 512M # 提高内存限制处理大型操作或插件 PHP_UPLOAD_MAX_FILESIZE: 50M # 允许上传更大的文件 PHP_MAX_EXECUTION_TIME: 300 # 延长脚本最大执行时间 PHP_OPCACHE_MEMORY_CONSUMPTION: 128 # 根据应用大小调整OPcache内存 # ... 其他配置以下是支持通过环境变量定制的主要配置项及其默认值你可以根据服务器资源和项目需求进行调整配置项目的 (php.ini directive)环境变量名默认值调优建议memory_limitPHP_MEMORY_LIMIT256M对于有大量内容或复杂插件如图像处理、导入导出的项目建议设置为512M或768M。max_execution_timePHP_MAX_EXECUTION_TIME120后台运行耗时任务如批量生成图片变体时可能需要增加。upload_max_filesizePHP_UPLOAD_MAX_FILESIZE20M根据你期望用户上传的文件大小调整。需与下一条post_max_size匹配或略小。post_max_sizePHP_POST_MAX_SIZE8M必须大于或等于upload_max_filesize。opcache.enablePHP_OPCACHE_ENABLE1生产环境务必保持为 1开启这是最重要的性能优化。opcache.validate_timestampsPHP_OPCACHE_VALIDATE_TIMESTAMPS0生产环境为 0不验证性能最佳。开发环境应为 1以便代码更改立即生效。opcache.memory_consumptionPHP_OPCACHE_MEMORY_CONSUMPTION256监控 OPcache 内存使用情况如果缓存命中率低或脚本数多可以适当增加如 512。实操心得对于生产环境我通常会在服务的环境变量中集中设置这些值。在 Kubernetes 中可以通过 ConfigMap 来管理在 Docker Compose for Production 中则使用env_file引用一个不提交到代码库的.env.production文件。记住开发环境和生产环境的 OPcache 设置特别是validate_timestamps应该是不同的。5. 常见问题排查与运维技巧实录即便有了完善的镜像和配置在实际开发和部署中你依然会遇到各种问题。下面是我在多年使用中积累的一些典型问题及其解决方案。5.1 权限问题容器内文件无法写入这是最常见的问题之一尤其是在本地开发使用卷挂载时。症状Craft 安装失败提示无法创建storage/目录下的文件或插件无法写入缓存。根本原因宿主机你的电脑上的文件所有者 UID/GID 与容器内的www-data用户UID 通常为 82不匹配。当你挂载本地目录时容器内的www-data用户可能没有权限写入这些文件。解决方案推荐在 Dockerfile 构建时修复如之前所述在COPY指令中使用--chownwww-data:www-data确保构建进镜像的文件所有权正确。开发时调整宿主机目录权限Linux/macOS在项目根目录执行sudo chown -R 82:82 .。这里的82是 Alpine 镜像中www-data用户的默认 UID。注意这会改变你本地文件的所有者。更优雅的开发方案使用一致的 UID创建一个自定义的 Dockerfile在构建时创建一个与宿主机用户 UID 相同的www-data用户。FROM craftcms/nginx:8.2-dev USER root # 假设你的宿主机用户UID是1000 RUN deluser www-data \ addgroup -g 1000 www-data \ adduser -u 1000 -G www-data -D -S -s /bin/sh www-data USER www-data COPY --chownwww-data:www-data . .使用 Docker 的命名卷对于vendor/、storage/这类不需要在宿主机直接编辑的目录可以考虑使用 Docker 命名卷完全避免权限问题。5.2 Xdebug 在本地无法连接 IDE症状设置了XDEBUG_CONFIG环境变量但断点不生效IDE 收不到调试连接。排查步骤确认使用的是-dev镜像。检查环境变量确保在docker-compose.yml中正确设置了XDEBUG_CONFIG: client_hosthost.docker.internal。对于 Linux 原生 Docker可能需要改为宿主机的局域网 IP如client_host192.168.1.100。检查 IDE 配置你的 IDE如 PHPStorm需要监听 Xdebug 连接通常端口 9003。并配置一个与PHP_IDE_CONFIG环境变量中serverName匹配的服务器映射将容器内的路径/app映射到你本地项目的路径。检查防火墙确保宿主机的防火墙允许来自 Docker 网络通常是172.x.x.x网段的入站连接到 Xdebug 端口默认 9003。启用 Xdebug 日志在环境变量中添加XDEBUG_CONFIG: client_hosthost.docker.internal log_level1 log/tmp/xdebug.log然后进入容器查看/tmp/xdebug.log文件里面会有详细的连接尝试信息。5.3 生产环境部署的注意事项镜像标签永远使用确定的版本标签如craftcms/nginx:8.2而不是latest。latest标签会变动可能导致不可预期的部署。不使用-dev镜像生产环境务必使用不带-dev后缀的镜像并确保没有携带 Xdebug 相关的环境变量。** secrets 管理**数据库密码、Craft 安全密钥等敏感信息绝不应硬编码在 Dockerfile 或 Compose 文件中。应使用 Docker SecretsSwarm、Kubernetes Secrets 或云服务商提供的密钥管理服务如 AWS Secrets Manager。健康检查像本地 Compose 文件里那样为生产容器配置健康检查。这对于编排平台如 Kubernetes的存活性和就绪性探针至关重要。日志处理确保将容器日志输出到标准输出stdout/stderr然后由 Docker 守护进程或日志驱动如json-file,journald, 或云平台的日志服务收集。Craft 自身的应用日志在storage/logs/目录下也需要考虑日志轮转和长期存储策略。5.4 从旧镜像迁移到新镜像仓库如前所述官方维护已转向craftcms/image。迁移通常很简单更新 Dockerfile 和 Compose 文件中的基础镜像引用。将FROM craftcms/php-fpm:8.2改为FROM craftcms/image:8.2-fpm。新的标签命名规则可能略有不同请查阅新仓库的文档。测试在测试环境中构建并运行新镜像确保所有功能正常特别是自定义的扩展安装和配置。关注新特性新的镜像仓库可能会引入优化、安全更新或新的可配置项值得花时间阅读其 README。回顾整个使用历程Craft CMS 的官方 Docker 镜像套件真正做到了“把复杂留给自己把简单留给开发者”。它通过清晰的镜像分层、合理的默认配置以及灵活的环境变量覆盖为开发者铺平了从本地开发到云端部署的道路。虽然原仓库即将归档但其设计思想将在新的craftcms/image项目中延续。掌握本文所述的这些核心概念、配置技巧和避坑经验无论你面对的是旧镜像还是新镜像都能从容不迫地构建出稳定、高效且可维护的 Craft CMS 容器化环境。记住容器化的终极目标不仅是让应用“跑起来”更是要让开发、测试、部署的整个生命周期变得可预测、可重复和自动化。