- 开发工具
- CLI
【免费下载链接】devenv
Fast, Declarative, Reproducible, and Composable Developer Environments using Nix
导读
本文讲解如何在 devenv 中声明式地搭建一套完整的本地 WordPress 开发环境:以 Caddy 作为 Web 服务器、PHP-FPM 执行 PHP 代码、MariaDB 存储数据,并用 wp-cli 自动完成 WordPress 下载与wp-config.php生成。读完本文你将掌握languages.php、services.mysql、services.caddy、tasks与进程依赖等 devenv 核心配置的配合方式,并能实现“一条devenv up命令即可打开 http://localhost:8000 开始安装 WordPress”的完整工作流。本文主体基于仓库文档 docs/src/content/docs/integrations/wordpress.md,并结合仓库源码与测试佐证底层实现细节。
环境总览:四个组件如何分工
整套环境由四个组件构成,各自职责如下:
- Caddy—— Web 服务器,接收 HTTP 请求并将其路由到 PHP-FPM;
- PHP-FPM—— FastCGI 进程管理器,负责执行 WordPress 的 PHP 代码;
- MariaDB—— 数据库服务器,存储 WordPress 的内容、用户与设置;
- wp-cli—— WordPress 命令行管理工具,用于下载核心、生成配置文件及后续管理。
在 devenv 中,这些组件不是手动启动的,而是全部以声明式配置写入devenv.nix,再由devenv up按依赖关系编排启动。仓库的集成测试 tests/wordpress/devenv.nix 展示了同样思路的自动化验证版本,可作为对照参考。
快速开始
- 在项目根目录创建
devenv.nix,写入下文完整配置; - 运行
devenv up—— 服务启动、数据库完成初始化、WordPress 自动下载、wp-config.php自动生成; - 浏览器访问 http://localhost:8000 完成 WordPress 安装。
整个流程中数据库 seeding、WordPress 下载与配置文件生成都是自动的,无需手工干预。
完整配置详解
以下为仓库文档给出的完整devenv.nix(见 docs/src/content/docs/integrations/wordpress.md),我们逐段拆解其含义。
{ pkgs, config, ... }: { # WordPress CLI for managing WordPress from the command line packages = [ pkgs.wp-cli ]; languages.php = { enable = true; version = "8.4"; # PHP extensions required by WordPress # Note: common extensions like xml, mbstring, curl are enabled by default extensions = [ "mysqli" # MySQL database connectivity "pdo_mysql" # PDO MySQL driver (used by some plugins) "gd" # Image manipulation (thumbnails, image editing) "zip" # Plugin/theme installation from zip files "intl" # Internationalization support "exif" # Image metadata reading ]; # PHP settings for WordPress ini = '' memory_limit = 256M upload_max_filesize = 64M post_max_size = 64M max_execution_time = 300 ''; # PHP-FPM pool configuration # FPM (FastCGI Process Manager) manages PHP worker processes fpm.pools.web = { settings = { "pm" = "dynamic"; # Dynamic process management "pm.max_children" = 10; # Maximum worker processes "pm.start_servers" = 2; # Workers to start initially "pm.min_spare_servers" = 1; # Minimum idle workers "pm.max_spare_servers" = 5; # Maximum idle workers }; }; }; # MariaDB database server services.mysql = { enable = true; package = pkgs.mariadb; # Create the WordPress database on first run initialDatabases = [{ name = "wordpress"; }]; # Create database user with access to WordPress database ensureUsers = [{ name = "wordpress"; password = "wordpress"; ensurePermissions = { "wordpress.*" = "ALL PRIVILEGES"; }; }]; }; # Caddy web server services.caddy = { enable = true; # Serve WordPress on http://localhost:8000 virtualHosts."http://localhost:8000" = { extraConfig = '' root * ${config.devenv.root}/wordpress # Pass PHP requests to PHP-FPM. php_fastcgi unix/${config.languages.php.fpm.pools.web.socket} # Serve static files directly file_server ''; }; }; # Download WordPress and write wp-config.php once MariaDB is ready and seeded. # Runs automatically as part of `devenv up` via the caddy process dependency. tasks."wordpress:setup" = { description = "Download WordPress and create wp-config.php"; after = [ "devenv:mysql:configure" ]; cwd = config.devenv.root; exec = '' set -e mkdir -p wordpress cd wordpress if [ ! -f wp-includes/version.php ]; then echo "Downloading WordPress..." wp core download else echo "WordPress already downloaded." fi if [ ! -f wp-config.php ]; then echo "Creating wp-config.php..." wp config create \ --dbname=wordpress \ --dbuser=wordpress \ --dbpass=wordpress \ --dbhost=127.0.0.1 echo "" echo "WordPress configured! Visit http://localhost:8000 to complete installation." else echo "wp-config.php already exists." fi ''; }; # Hold caddy until WordPress is on disk so the first request isn't a 404. processes.caddy.after = [ "wordpress:setup" ]; # Show helpful instructions when entering the shell enterShell = '' echo "" echo "WordPress Development Environment" echo "==================================" echo "" echo "Run devenv up to start services and provision WordPress, then open:" echo " http://localhost:8000" echo "" echo "Database credentials (for wp-config.php):" echo " Host: 127.0.0.1" echo " Database: wordpress" echo " User: wordpress" echo " Password: wordpress" echo "" ''; }1.packages:引入 wp-cli
packages = [ pkgs.wp-cli ];将 wp-cli 加入 shell 环境,使其在devenv shell或devenv up后可直接使用(如wp core download、wp config create、后续的插件/主题管理)。devenv up运行的任务同样继承该环境,因此wordpress:setup任务内可以调用wp命令。
2.languages.php:PHP 运行时与 PHP-FPM
languages.php = { enable = true; version = "8.4"; extensions = [ "mysqli" "pdo_mysql" "gd" "zip" "intl" "exif" ]; ini = '' memory_limit = 256M upload_max_filesize = 64M post_max_size = 64M max_execution_time = 300 ''; fpm.pools.web = { ... }; };enable = true启用 PHP 工具链;version指定 PHP 大版本。从 src/modules/languages/php.nix 的实现看,版本解析优先使用nixpkgs中对应php<version>包,若不可用则从github:fossar/nix-phps输入拉取(该输入的声明方式可参考 examples/caddy-php/devenv.yaml 中的phps输入),解析不到会抛出 "PHP version ... is not available" 错误。extensions列出 WordPress 必需的扩展:mysqli负责 MySQL 连接、pdo_mysql供部分插件使用、gd用于图片缩放与编辑、zip支持从压缩包安装插件/主题、intl提供国际化、exif读取图片元数据。需要说明的是,xml、mbstring、curl等常用扩展默认已启用,无需重复声明;php.nix通过configurePackage在默认启用扩展之上追加extensions列表中的项。ini追加php.ini指令:memory_limit = 256M限制单脚本内存,upload_max_filesize与post_max_size提升媒体上传上限,max_execution_time = 300放宽脚本超时,适合主题/插件安装与媒体处理。从源码看,ini内容会被拼入该池生成的php.ini(phpIni拼接php.ini与用户指令),同时若启用了services.mysql,模块还会自动注入pdo_mysql.default_socket/mysqli.default_socket指向本机 MySQL 的 Unix socket。fpm.pools.web定义一个名为web的 PHP-FPM 池。pm = dynamic表示动态调整 worker 数量,配合pm.max_children(最大 worker 数)、pm.start_servers(初始 worker 数)、pm.min_spare_servers/pm.max_spare_servers(空闲 worker 上下限)。注意指令名必须加引号(如"pm.max_children")。从php.nix的poolOpts实现可见,池的socket是只读选项,默认路径为${config.env.DEVENV_RUNTIME}/php-fpm/web.sock,因此配置中可直接引用${config.languages.php.fpm.pools.web.socket}获取该路径;每个池会生成一个名为phpfpm-<pool>的进程,web池对应phpfpm-web进程。
3.services.mysql:MariaDB 数据库服务
services.mysql = { enable = true; package = pkgs.mariadb; initialDatabases = [{ name = "wordpress"; }]; ensureUsers = [{ name = "wordpress"; password = "wordpress"; ensurePermissions = { "wordpress.*" = "ALL PRIVILEGES"; }; }]; };package = pkgs.mariadb指定数据库引擎为 MariaDB(services.mysql的默认包即pkgs.mariadb,见 src/modules/services/mysql.nix);initialDatabases声明首次启动时创建的数据库列表,这里创建一个空的wordpress库(schema属性可省略,省略时创建空库);ensureUsers声明需要确保存在的数据库用户:用户wordpress密码wordpress,并对wordpress.*授予ALL PRIVILEGES(ensurePermissions的键为数据库.表,值为逗号分隔的 SQL 权限)。
这些初始化动作并不是发生在 shell 里的,而是由一个自动生成的 oneshot 任务devenv:mysql:configure完成的:mysql.nix在processes.mysql.before中声明它,并在其configureScript中按initialDatabases/ensureUsers生成建库、建用户与授权 SQL。数据目录为${config.env.DEVENV_STATE}/mysql(环境变量MYSQL_HOME),监听端口则通过进程端口分配机制预留(默认3306),并同时注入MYSQL_TCP_PORT、MYSQL_UNIX_PORT等环境变量。
4.services.caddy:Web 服务器
services.caddy = { enable = true; virtualHosts."http://localhost:8000" = { extraConfig = '' root * ${config.devenv.root}/wordpress php_fastcgi unix/${config.languages.php.fpm.pools.web.socket} file_server ''; }; };virtualHosts声明虚拟主机,键为站点地址http://localhost:8000,extraConfig的内容逐字写入 Caddyfile 的站点块(见 src/modules/services/caddy.nix 中vhostToConfig的拼接逻辑);root * ${config.devenv.root}/wordpress将站点根目录指向仓库下的wordpress/目录;php_fastcgi unix/...通过 PHP-FPM 的 Unix socket 转发 PHP 请求,该指令会自动处理 WordPress 的固定链接(pretty permalinks)——不存在的路径会回落到index.php;file_server直接提供静态文件服务。
启用后模块会生成processes.caddy进程,用caddy run --config <生成的配置文件>启动;配置会先经过caddy fmt格式化再caddy adapt转成 JSON(源码中formattedConfig/adaptedConfig的实现),extraConfig里的指令因此可以放心编写为可读的 Caddyfile 风格。
5.tasks."wordpress:setup":自动下载并配置 WordPress
tasks."wordpress:setup" = { description = "Download WordPress and create wp-config.php"; after = [ "devenv:mysql:configure" ]; cwd = config.devenv.root; exec = '' set -e mkdir -p wordpress cd wordpress if [ ! -f wp-includes/version.php ]; then echo "Downloading WordPress..." wp core download else echo "WordPress already downloaded." fi if [ ! -f wp-config.php ]; then echo "Creating wp-config.php..." wp config create \ --dbname=wordpress \ --dbuser=wordpress \ --dbpass=wordpress \ --dbhost=127.0.0.1 echo "" echo "WordPress configured! Visit http://localhost:8000 to complete installation." else echo "wp-config.php already exists." fi ''; };这段配置是整套环境的“粘合层”:
after = [ "devenv:mysql:configure" ]声明它必须等数据库初始化任务成功后才执行,保证建库、建用户、授权都已就绪;cwd = config.devenv.root指定工作目录为项目根目录;- 脚本具备幂等性:通过检查
wp-includes/version.php判断 WordPress 是否已下载,通过检查wp-config.php判断配置是否已生成,重复运行devenv up不会重复下载或覆盖已有配置; wp config create使用--dbhost=127.0.0.1以 TCP 方式连接数据库(wp-config.php 中的DB_HOST即 127.0.0.1)。
关于任务机制:devenv 的tasks模块(见 src/modules/tasks.nix)支持after/before/wantedBy等依赖声明,after默认语义是等前置任务成功完成(后缀@succeeded);同时devenv:mysql:configure本身是在services.mysql模块中按“MySQL 就绪后”这一条件生成的(processes.mysql.ready通过mysqladmin ping探测,devenv:mysql:configure以wantedBy = [ "devenv:processes:mysql" ]挂接,见mysql.nix末尾),因此这条依赖链实际是“MariaDB 就绪 → 建库建用户 → 下载 WordPress 并写配置”。
6.processes.caddy.after:让首请求绝不 404
processes.caddy.after = [ "wordpress:setup" ];Caddy 进程要等到wordpress:setup成功之后才启动,确保第一个 HTTP 请求到达时文档根目录下已经有真实的 WordPress 文件,而不是一个空目录。这是文档明确强调的“Hold caddy until WordPress is on disk so the first request isn't a 404”这一设计意图的实现。
7.enterShell:进入环境时的引导信息
enterShell在进入开发 shell 时打印使用说明与数据库凭据(Host/Database/User/Password),方便团队成员无需翻文档即可上手。对于本文示例,凭据均为wordpress(主机127.0.0.1),仅用于本地开发。
工作原理:devenv up的依赖执行图
运行devenv up时,devenv 按依赖关系依序执行以下节点:
devenv:mysql:configure—— MariaDB 就绪后作为 oneshot 任务运行,创建wordpress数据库、wordpress用户并授予权限(由services.mysql模块自动生成,其内容来自initialDatabases与ensureUsers);wordpress:setup—— 在devenv:mysql:configure成功之后运行,下载 WordPress 核心并写入wp-config.php;- Caddy—— 仅在
wordpress:setup完成后启动,避免首个 HTTP 请求命中空文档根目录; - PHP-FPM—— 通过 Unix socket 暴露 FastCGI 接口,Caddy 的
php_fastcgi指令将.php请求代理给它,并自动处理 WordPress 固定链接(不存在的路径回落到index.php)。
这一编排充分体现了 devenv 的“进程 + 任务”双层依赖模型:进程(processes.*)提供常驻服务,任务(tasks.*)提供一次性初始化,二者通过after/before/wantedBy互相关联。
底层调用链速览
- PHP-FPM 进程:
languages.php.fpm.pools中每个池生成processes.phpfpm-<pool>,其启动脚本用php-fpm -F -y <pool.conf> -c <php.ini>前台运行,池配置由 src/modules/languages/php.nix 的fpmCfgFile生成(含[global]与池段落,error_log默认落在${config.env.DEVENV_STATE}/php-fpm/php-fpm.log); - MySQL 初始化:见 src/modules/services/mysql.nix 的
configureScript,逐库检查information_schema.schemata避免重复创建,逐用户执行CREATE USER IF NOT EXISTS与GRANT; - Caddy 配置:见 src/modules/services/caddy.nix,
virtualHosts被序列化为 Caddyfile 站点块,随后格式化并适配为 JSON 配置后启动。
仓库测试 tests/wordpress/devenv.nix 验证了类似链路的可运行性:它通过processes.phpfpm-web.ready.exec = "test -S .../web.sock"探测 FPM socket,通过processes.caddy.ready.http.get请求/index.php并断言返回成功,还让index.php用mysqli实际连接数据库后输出OK——这相当于用自动化方式复现了本文文档描述的整条服务链路。
故障排查
数据库连接错误
devenv up会自动完成数据库初始化。如果页面提示 "Error establishing database connection",先确认数据库与用户是否确实存在:
mysql -u wordpress -pwordpress -h 127.0.0.1 -e "SHOW DATABASES;"如果wordpress用户缺失,说明devenv:mysql:configure任务没有执行,可检查日志:
devenv tasks list devenv tasks run devenv:mysql:configure另外,旧版本搭建残留的.devenv/state/mysql也可能导致此问题(数据目录由MYSQL_HOME指向${config.env.DEVENV_STATE}/mysql,见mysql.nix);清理该目录后重新运行devenv up即可。
端口 8000 被占用
若其他服务占用了 8000 端口,只需在 Caddy 配置中改端口即可:
services.caddy.virtualHosts."http://localhost:8080" = { ... };若需要固定端口分配,可参考测试 tests/wordpress/devenv.nix 的做法:通过config.processes.caddy.ports.http.value读取进程端口分配结果,并利用processes.caddy.ports.http.allocate = 8000声明期望端口,避免硬编码。
PHP 扩展缺失
如果 WordPress 报告缺少扩展,把它们加入extensions列表:
languages.php.extensions = [ "mysqli" "imagick" # Add additional extensions as needed ];修改配置后重新进入 shell 或重跑devenv up让 PHP 包重建。
进阶配置
添加 Redis 做缓存
Redis 通过缓存数据库查询显著改善 WordPress 性能:
services.redis.enable = true; languages.php.extensions = [ # ... other extensions ... "redis" ];然后在 WordPress 后台安装 "Redis Object Cache" 之类的对象缓存插件。值得说明的是:即便不手动添加"redis"扩展,只要启用services.redis,src/modules/languages/php.nix 的config部分也会自动把redis扩展加入languages.php.extensions(lib.optionals config.services.redis.enable [ "redis" ]);services.redis模块(见 src/modules/services/redis.nix)默认监听127.0.0.1:6379并生成processes.redis进程。因此手动声明扩展更多是显式表达意图。
添加 Xdebug 进行调试
在 IDE 中启用逐步调试:
languages.php.extensions = [ # ... other extensions ... "xdebug" ]; languages.php.ini = '' memory_limit = 256M xdebug.mode = debug xdebug.start_with_request = yes xdebug.client_port = 9003 '';xdebug.mode = debug开启调试模式;xdebug.start_with_request = yes让每个请求都触发调试会话(也可改为trigger配合 IDE 的监听行为);xdebug.client_port = 9003指定调试器监听端口(Xdebug 3 的默认端口)。
注意这些ini指令与上面 PHP 版本的memory_limit一起写即可,同一languages.php.ini选项会被合并进最终的php.ini。
使用本地证书启用 HTTPS
部分插件强制要求 HTTPS,可用本地证书解决:
certificates = [ "localhost" ]; services.caddy.virtualHosts."https://localhost" = { extraConfig = '' tls ${config.env.DEVENV_STATE}/mkcert/localhost.pem ${config.env.DEVENV_STATE}/mkcert/localhost-key.pem root * ${config.devenv.root}/wordpress php_fastcgi unix/${config.languages.php.fpm.pools.web.socket} file_server ''; };certificates选项由 mkcert 集成模块(src/modules/integrations/mkcert.nix)实现:对列表中的每个域名生成本地 CA 签名证书,默认输出到${config.env.DEVENV_STATE}/mkcert下(localhost域名对应localhost.pem与localhost-key.pem)。模块同时设置env.CAROOT指向该目录,并在证书生成任务(native 进程管理器下为devenv:mkcert:setup)完成后才启动依赖证书的进程。
两点注意事项:
tls指令直接指定证书与私钥文件路径,Caddy 不再走自动 ACME;- HTTPS 默认 443 端口需要特权:建议改用高位端口(如 8443),或自行配置系统允许绑定特权端口。修改端口只需替换
virtualHosts的键,例如"https://localhost:8443"。
小结
这套 WordPress 开发环境配置的核心价值在于完全声明式与可复现:PHP 版本、扩展、PHP-FPM 池参数、数据库初始化、Web 服务器与任务编排全部写在devenv.nix中,任何团队成员拿到仓库后执行devenv up即可得到一致的本地环境;而wordpress:setup任务的幂等写法(按文件存在性判断是否下载/写配置)让重复启动也安全无副作用。若想进一步深入,建议阅读:
- src/modules/languages/php.nix —— PHP 版本解析、扩展组装与 PHP-FPM 池生成
- src/modules/services/mysql.nix —— 数据库初始化任务与用户授权实现
- src/modules/services/caddy.nix —— Caddyfile 组装与进程启动
- src/modules/tasks.nix —— 任务依赖(after/before/wantedBy)机制
- tests/wordpress/devenv.nix —— 本环境的自动化验证版本
- examples/caddy-php/devenv.nix 与 examples/caddy-php/devenv.yaml —— 更简化的 Caddy + PHP-FPM 参考示例
- 开发工具
- CLI
【免费下载链接】devenv
Fast, Declarative, Reproducible, and Composable Developer Environments using Nix
相关推荐
Argo Workflows 使用 Nix 与 devenv 搭建可复现本地开发环境实战指南
Argo Workflows 使用 Nix 与 devenv 搭建可复现本地开发环境实战指南 Nix 是一种强调可复现构建环境的包管理器与构建工具,Argo W
云原生容器编排工作流自动化任务调度后端Gutenberg 开发必学:使用 wp-env 零配置搭建本地 WordPress 开发环境
Gutenberg 开发必学:使用 wp env 零配置搭建本地 WordPress 开发环境 wp env (即 @wordpress/env npm 包)是
后端前端Laravel on Docker 实战:用 Laradock 一键搭建 Nginx + PHP-FPM + MySQL + Redis 开发环境
Laravel on Docker 实战:用 Laradock 一键搭建 Nginx + PHP FPM + MySQL + Redis 开发环境 导读 本文以
后端开发工具DevOps
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考