Craft CMS容器化部署实战:Docker Compose编排与生产环境配置指南 1. 项目概述为什么选择容器化Craft CMS如果你和我一样经常需要为不同的客户部署Craft CMS项目或者需要在本地、测试、生产多个环境之间保持一致性那你一定对“环境差异”这个老问题深恶痛绝。PHP版本不匹配、扩展缺失、目录权限问题、数据库连接配置不同……这些琐碎但致命的问题足以让一个下午的部署工作变成一场噩梦。“craftcms/docker”这个项目就是针对这个痛点的一剂良药。它不是一个简单的Docker镜像而是一个围绕Craft CMS最佳实践构建的、开箱即用的容器化解决方案。简单来说它把运行Craft CMS所需的所有“零件”——包括特定版本的PHP、必要的扩展如GD、Imagick、Intl、Composer、Node.js环境甚至一个用于开发的MailHog邮件捕获工具——都预先打包好并配置好了它们之间协同工作的方式。这样做最直接的好处是“一致性”。无论是在你的MacBook上、同事的Windows电脑上还是在云服务器的Ubuntu系统里只要Docker能跑起来你的Craft CMS应用运行环境就是一模一样的。这彻底消除了“在我机器上是好的”这类经典问题。对于开发者而言这意味着新成员入职时不再需要花半天时间配置复杂的本地环境一条docker-compose up -d命令就能让项目跑起来。对于运维而言部署变得可预测和可重复大大降低了上线风险。这个项目特别适合以下几类人独立开发者或小型团队希望快速搭建和复制开发环境需要为多个Craft CMS项目维护不同PHP版本环境的开发者以及任何希望提升开发部署流程标准化和自动化程度的Craft CMS使用者。接下来我将带你深入拆解这个项目的设计思路、核心配置并分享从零开始使用它到投入实际开发的完整过程以及我踩过的一些坑和总结的技巧。2. 核心镜像与项目结构深度解析2.1 官方镜像的层次与选型逻辑“craftcms/docker”项目在Docker Hub上提供了一系列标签化的镜像理解这些标签的命名规则是正确选型的第一步。其基本命名模式是craftcms/php:{PHP版本号}-{变体}。首先看PHP版本。Craft CMS对PHP版本有明确要求例如Craft 4需要PHP 8.0Craft 5需要PHP 8.2。官方镜像提供了从7.4到8.3等多个主流版本。我的建议是永远使用与你目标生产环境一致且满足Craft CMS最低要求的PHP版本。例如如果你的服务器运行PHP 8.2那么本地开发也应选择craftcms/php:8.2系列的镜像以避免因版本差异导致的语法兼容性或扩展行为不一致问题。其次是变体Variant这是镜像功能集的分层-apache: 这是最常用、最全面的变体。它基于官方的php:apache镜像内置了Apache HTTP Server和mod_rewrite等常用模块。Apache的.htaccess文件对于Craft CMS的URL路由、资源重写和安全规则至关重要。如果你不打算深度定制Web服务器或者你的项目严重依赖.htaccess规则很多共享主机和经典部署都如此那么-apache变体是默认且稳妥的选择。-fpm: 这个变体仅包含PHP-FPMFastCGI Process Manager进程管理器。它不包含Web服务器需要你额外配置一个Nginx或Apache作为反向代理。这种组合Nginx PHP-FPM是追求高性能、高并发场景下的标准架构。Nginx负责处理静态文件如图片、CSS、JS效率极高而动态PHP请求则转发给PHP-FPM进程处理。如果你部署在Kubernetes、或使用云平台的应用容器服务通常需要此变体。-cli: 仅包含PHP命令行环境。它用于执行Composer安装、Craft控制台命令craft setupcraft migrate/all等、队列任务处理如果用了队列等。在docker-compose配置中我们通常会让Web容器apache或fpm依赖一个cli服务来执行应用初始化脚本。注意-apache变体实际上也包含了CLI能力你可以在容器内执行php craft命令。但在多服务编排时明确区分一个专门用于执行命令的cli服务是更清晰的做法可以避免在Web容器内进行可能影响服务的操作。2.2 项目目录结构与职责划分一个典型的基于此镜像的Craft CMS项目目录结构如下所示。理解每个部分的作用是灵活运用和自定义配置的基础。your-craft-project/ ├── docker-compose.yaml # 服务编排的核心配置文件 ├── .env # 环境变量定义切勿提交至Git ├── .env.example # 环境变量示例模板 ├── config/ # Craft CMS配置文件目录 │ └── general.php ├── modules/ # 自定义模块目录 ├── templates/ # 前端模板目录 ├── web/ # Web根目录Apache/Nginx服务于此 │ ├── cpresources/ # 控制面板资源缓存 │ ├── index.php # Craft入口文件 │ └── .htaccess # Apache重写规则-apache变体需要 ├── composer.json # PHP依赖定义 ├── package.json # Node.js依赖定义如果使用Vite/Webpack └── docker/ ├── php/ # 自定义PHP配置 │ └── php.ini # 覆盖默认PHP配置 ├── nginx/ # 自定义Nginx配置如果使用-fpm变体 │ └── default.conf └── entrypoint.d/ # 自定义入口点脚本目录 └── 01-set-permissions.sh关键目录解析web/: 这是容器内Web服务器的文档根目录DocumentRoot。对于-apache变体Apache会直接服务于此目录。所有通过浏览器访问的请求都会指向这里的index.php。cpresources/目录需要写入权限用于存储控制面板生成的各种资源文件。docker/: 这是用于存放Docker相关自定义配置的目录。你可以在这里覆盖默认的PHP配置php.ini例如调整内存限制、上传文件大小等。entrypoint.d/目录非常有用官方镜像的入口点脚本会执行此目录下所有可执行的.sh文件这为我们提供了在容器启动前执行自定义命令如设置文件权限、安装特定扩展的钩子。.env文件: 这是项目的生命线。它存储了所有环境相关的敏感和可变配置如数据库连接字符串、安全密钥、调试模式开关等。docker-compose.yaml和Craft的config/general.php都会读取这个文件。务必确保.env文件在.gitignore中而将.env.example仅包含键名和示例值提交到版本库。3. 从零开始的完整编排与配置实战3.1 Docker Compose 配置详解下面是一个功能相对完整的docker-compose.yaml示例适用于大多数使用-apache变体的开发场景。我将逐段解释其设计意图。version: 3.8 services: # 主应用服务 - 使用Apache变体 app: image: craftcms/php:8.2-apache container_name: craft-app restart: unless-stopped working_dir: /app volumes: - .:/app - ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro ports: - 8080:80 environment: - DB_DSN${DB_DSN} - DB_USER${DB_USER} - DB_PASSWORD${DB_PASSWORD} - SECURITY_KEY${SECURITY_KEY} - DEV_MODE${DEV_MODE} depends_on: - database - mailhog networks: - craft-network # 专门用于执行命令行工具的服务 cli: image: craftcms/php:8.2-cli container_name: craft-cli restart: no # CLI容器通常执行完命令就退出不需要常驻重启 working_dir: /app volumes: - .:/app - ~/.composer:/tmp/composer # 挂载Composer缓存加速安装 environment: - DB_DSN${DB_DSN} - DB_USER${DB_USER} - DB_PASSWORD${DB_PASSWORD} - SECURITY_KEY${SECURITY_KEY} networks: - craft-network # 这个服务默认不启动仅在需要时运行命令如docker-compose run --rm cli composer install # MySQL数据库服务 database: image: mysql:8.0 container_name: craft-db restart: unless-stopped command: --default-authentication-pluginmysql_native_password # 兼容旧客户端 environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: ${DB_DATABASE} MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} volumes: - db_data:/var/lib/mysql - ./docker/mysql/conf.d:/etc/mysql/conf.d:ro # 可挂载自定义MySQL配置 ports: - 3306:3306 # 暴露端口便于本地数据库工具连接 networks: - craft-network healthcheck: # 健康检查确保数据库就绪后app再启动 test: [CMD, mysqladmin, ping, -h, localhost, -u${DB_USER}, -p${DB_PASSWORD}] interval: 10s timeout: 5s retries: 5 # 邮件捕获服务用于开发环境拦截和查看所有外发邮件 mailhog: image: mailhog/mailhog container_name: craft-mailhog restart: unless-stopped ports: - 8025:8025 # Web UI端口用于查看邮件 - 1025:1025 # SMTP端口供Craft CMS发送邮件 networks: - craft-network volumes: db_data: # 命名卷持久化数据库数据 networks: craft-network: driver: bridge关键配置点解析卷Volumes映射- .:/app将当前项目目录挂载到容器的/app目录实现了代码的实时同步。对本地文件的任何修改都会立即反映在容器中。这是开发模式的核心。环境变量传递environment部分将.env文件中定义的值注入容器。Craft CMS会自动读取这些以CRAFT_或DB_开头的环境变量。这种设计实现了配置与代码的分离。网络Networks所有服务加入同一个自定义网络craft-network。在这个网络内服务间可以使用服务名作为主机名进行通信。例如在app容器中你可以使用database这个主机名连接到MySQL服务无需知道其IP地址。这简化了配置提高了可移植性。健康检查Healthcheck为database服务配置健康检查是生产就绪思维。它确保app容器通过depends_on会等待数据库真正可接受连接后才启动避免了启动时因数据库未就绪而导致的连接失败。端口映射将容器的80端口映射到主机的8080端口避免了与本地可能已运行的Web服务如Mac的Apache冲突。MailHog的UI8025和SMTP1025端口也一并映射出来。3.2 环境变量与Craft配置的衔接配套的.env文件可能如下所示# 数据库配置 DB_DSNmysql:hostdatabase;port3306;dbnamecraftdb;charsetutf8mb4 DB_DATABASEcraftdb DB_USERcraftuser DB_PASSWORDStrongPassword123! DB_ROOT_PASSWORDEvenStrongerRootPass! # Craft CMS 安全配置 SECURITY_KEYYourGenerated32CharLongRandomStringHere # 应用配置 DEV_MODEtrue SITE_URLhttp://localhost:8080在Craft CMS的config/general.php中你可以这样引用环境变量?php return [ devMode getenv(DEV_MODE) true, securityKey getenv(SECURITY_KEY), db [ dsn getenv(DB_DSN), user getenv(DB_USER), password getenv(DB_PASSWORD), schema getenv(DB_DATABASE), tablePrefix getenv(DB_TABLE_PREFIX) ?: , ], ];这种模式的美妙之处在于当你将应用部署到生产环境如云服务器、Kubernetes集群时你只需要在对应平台设置同样的环境变量而无需修改任何一行PHP代码。config/general.php文件可以在所有环境中保持完全相同。3.3 初始化与日常开发工作流假设项目是从一个全新的Craft CMS安装开始。步骤一启动基础设施# 克隆或创建项目目录后进入目录 cd your-craft-project # 创建 .env 文件并填写配置参考上面的示例 cp .env.example .env # 使用编辑器修改 .env 文件中的值尤其是 SECURITY_KEY 和数据库密码 # 启动数据库和MailHog等依赖服务 docker-compose up -d database mailhog步骤二使用CLI容器安装Craft CMS# 使用cli服务运行Composer安装Craft # --rm 参数表示命令执行完毕后自动清理容器 docker-compose run --rm cli composer create-project craftcms/craft . # 安装完成后运行Craft安装向导 # 这里的所有配置数据库连接、安全密钥等都会自动从 .env 文件读取 docker-compose run --rm cli php craft install在安装向导中当询问数据库信息时你会发现主机名、数据库名、用户名和密码已经自动填充来自.env你通常只需要确认即可。步骤三启动应用并访问# 启动主应用服务 docker-compose up -d app # 查看日志确认启动无误 docker-compose logs -f app现在打开浏览器访问http://localhost:8080你应该能看到Craft CMS的默认首页。访问http://localhost:8025则可以打开MailHog的Web界面查看Craft发送的所有邮件如用户注册验证邮件。日常开发命令运行Craft控制台命令docker-compose run --rm cli php craft [command]例如docker-compose run --rm cli php craft clear-caches/all安装PHP包docker-compose run --rm cli composer require vendor/package安装Node包由于镜像包含了Node.js你也可以在app容器内运行npm但更推荐在主机上运行如果已安装或者为Node操作也创建一个临时容器。查看日志docker-compose logs -f app或docker-compose logs -f database4. 高级配置、优化与生产环境考量4.1 性能调优与自定义配置默认配置适用于开发但对于资源紧张的环境或追求更高性能时可以进行调优。1. PHP配置优化 (docker/php/php.ini):; 提高内存限制处理大型操作或插件时可能需要 memory_limit 256M ; 提高上传文件大小限制 upload_max_filesize 64M post_max_size 64M ; 调整OPcache用于生产模式 opcache.enable1 opcache.memory_consumption128 opcache.interned_strings_buffer8 opcache.max_accelerated_files10000 opcache.revalidate_freq2 opcache.fast_shutdown1通过卷映射这个自定义的php.ini会覆盖容器内的默认配置。2. Apache/Nginx优化对于-apache变体你可以通过创建docker/apache/httpd.conf或.htaccess文件来配置。对于-fpm变体搭配Nginx一个基本的docker/nginx/default.conf可能如下server { listen 80; server_name localhost; root /app/web; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass app:9000; # 注意这里使用服务名‘app’和PHP-FPM端口9000 fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location ~ /\.(ht|git) { deny all; } }然后在docker-compose.yaml中为app服务使用-fpm镜像添加一个Nginx服务并挂载此配置。3. 使用自定义入口点脚本有时需要在容器启动时执行特定操作例如确保web/cpresources目录有正确的权限。创建docker/entrypoint.d/01-set-permissions.sh#!/bin/bash # 确保web目录下的特定目录可写 chown -R www-data:www-data /app/web/cpresources chmod -R 775 /app/web/cpresources echo Permissions set for cpresources.记得给脚本添加执行权限chmod x docker/entrypoint.d/01-set-permissions.sh。官方镜像的入口点会自动执行/docker-entrypoint.d/和/app/docker/entrypoint.d/下的所有脚本。4.2 向生产环境演进的关键调整开发环境的docker-compose.yaml直接用于生产是危险的。以下是向生产环境过渡时必须考虑的变化移除端口映射依赖内部网络生产环境不应将数据库端口3306暴露给外网。删除database服务的ports配置仅让app服务通过内部网络访问它。应用对外的访问通过负载均衡器或云平台的路由规则暴露80/443端口。使用特定的生产镜像标签避免使用latest标签。为craftcms/php镜像指定一个明确的版本号例如craftcms/php:8.2.20-apache。这能保证部署的一致性。分离构建与运行在CI/CD流水线中先构建一个包含所有代码和依赖的应用镜像而不是在运行时通过卷挂载代码。这可以通过编写Dockerfile来实现例如FROM craftcms/php:8.2-apache COPY --fromcomposer:2 /usr/bin/composer /usr/bin/composer WORKDIR /app COPY composer.json composer.lock ./ RUN composer install --no-dev --optimize-autoloader --no-interaction --no-progress COPY . . RUN chown -R www-data:www-data /app/web/cpresources然后在docker-compose.prod.yaml中将app服务的image改为build: .并移除开发用的代码卷挂载。管理敏感信息生产环境的.env文件或环境变量必须通过安全的秘密管理服务如Docker Swarm secrets, Kubernetes Secrets, AWS Secrets Manager来注入绝不能硬编码在镜像或代码库中。配置日志与监控将容器日志导向集中式日志系统如ELK Stack, Loki。为服务配置资源限制cpu_shares,mem_limit并设置适当的健康检查。4.3 多项目与团队协作实践当团队共同开发或多个项目并存时以下实践能提升效率统一的开发环境规范将经过验证的docker-compose.yaml和配套脚本作为团队模板。新成员克隆项目后只需docker-compose up -d即可获得完全一致的环境。使用Hosts文件别名为每个项目设置不同的本地域名如project-a.test,project-b.test并在docker-compose.yaml中为app服务映射到80端口。然后修改本机hosts文件127.0.0.1 project-a.test。这样可以通过更友好的URL访问项目且能同时运行多个项目而无需记住不同的端口号。共享的Composer缓存卷可以为cli服务配置一个命名卷来缓存Composer包加速所有项目的依赖安装在volumes部分定义composer-cache:/tmp/composer并在cli服务中挂载它。文档化操作流程在项目README.md中清晰列出常用命令如启动、关闭、安装、更新、查看日志等。这能减少沟通成本。5. 常见问题、故障排查与实战技巧5.1 启动与连接问题排查表问题现象可能原因排查步骤与解决方案访问localhost:8080报“Connection refused”或无法连接。1. 容器未运行。2. 端口被占用。3. 防火墙/安全软件阻止。1.docker-compose ps检查app服务状态是否为 “Up”。2.docker-compose logs app查看启动日志。3.netstat -an | grep 8080(Linux/Mac) 或Get-NetTCPConnection -LocalPort 8080(PowerShell) 检查主机端口占用可修改docker-compose.yaml中的端口映射如8081:80。页面显示“502 Bad Gateway”或“No input file specified.”。1. PHP-FPM未启动或配置错误使用-fpm变体时。2. 文件路径映射错误index.php不存在于容器内。1. 确认使用的是-apache还是-fpm镜像并检查对应Web服务器配置。2.docker-compose exec app ls -la /app/web/确认入口文件存在。3. 检查docker-compose.yaml中的volumes映射路径是否正确。Craft安装时提示“无法连接到数据库”。1. 数据库服务未启动。2. 环境变量配置错误。3. 数据库网络不通。1.docker-compose ps确认database服务为 “Up”。2.docker-compose exec app env | grep DB_检查环境变量是否已正确注入应用容器。3.docker-compose exec app ping database测试从容器的网络连通性。4. 检查.env文件中的DB_DSN主机名是否为database服务名。控制面板能打开但保存内容或上传文件时提示“权限不足”。web/cpresources或storage/目录在容器内不可写。1.docker-compose exec app ls -la /app/web/查看目录所有者和权限。2. 确保目录所有者为www-dataApache用户。使用自定义入口点脚本见4.1节或启动后手动执行docker-compose exec app chown -R www-data:www-data /app/web/cpresources /app/storage。Composer安装依赖速度极慢。未配置Composer中国镜像或缓存。1. 在cli服务的命令中临时使用镜像docker-compose run --rm cli composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/2. 更好的方法在主机或通过卷挂载全局Composer配置或使用前面提到的共享缓存卷。修改.env文件后应用未读取新值。PHP-FPM或Apache进程缓存了环境变量。重启应用容器docker-compose restart app。对于生产环境可能需要重建容器。5.2 性能与资源管理技巧限制容器资源在docker-compose.yaml中为服务添加资源限制防止单个容器耗尽主机资源。app: deploy: resources: limits: cpus: 1 memory: 512M reservations: memory: 256M优化镜像构建如果使用自定义Dockerfile充分利用构建缓存。将不常变动的文件如composer.json复制和依赖安装步骤放在前面将经常变动的源代码复制放在后面。使用.dockerignore文件在项目根目录创建.dockerignore排除不需要进入镜像的文件如.git,node_modules, 测试文件日志文件可以显著减小镜像体积加速构建过程。选择性同步卷在Mac或Windows上Docker Desktop的文件同步Volume性能可能不佳。对于大型的node_modules目录可以考虑通过.dockerignore排除并在容器内单独安装Node依赖而不是从主机同步。5.3 调试与开发体验提升Xdebug集成虽然官方镜像未预装Xdebug但可以轻松添加。创建一个自定义Dockerfile基于craftcms/php镜像安装并启用Xdebug扩展。然后配置你的IDE如PHPStorm监听Docker容器的调试连接。这能实现断点调试、变量查看等强大功能。使用docker-compose exec进行交互docker-compose exec app bash可以让你进入运行中的app容器内部像使用SSH一样执行命令非常适合进行临时调试或探索容器内部状态。日志聚合查看docker-compose logs -f --tail50可以实时查看所有服务的最后50行日志并持续跟踪。这对于观察跨服务交互如应用连接数据库的问题非常有用。数据库管理虽然可以通过端口映射用本地工具如TablePlus, Sequel Ace连接也可以在cli容器内使用命令行工具docker-compose run --rm cli mysql -hdatabase -ucraftuser -p craftdb。经过几个项目的实践我个人最深的体会是将Craft CMS容器化最大的收益并非来自于技术本身有多高深而是它所带来的确定性和自动化。它把环境配置这项“脏活累活”变成了可版本化、可重复的代码。新同事入职的第一天从“配置环境”变成了“开始写代码”这种效率提升是实实在在的。对于需要维护多个不同PHP版本老项目的团队来说这更是一个救星。当然初期需要花些时间理解和调试Docker的配置但这份投资在项目的整个生命周期里会以无数倍的时间节省回报给你。最后一个小建议把你的docker-compose.yaml和相关的Docker配置也纳入版本控制并随着项目需求的变化而迭代它将成为你项目基础设施最重要的文档之一。