Deployer Provision Recipe 详解:从裸机到可部署 Web 服务器的全自动配置指南
【免费下载链接】deployerThe PHP deployment tool with support for popular frameworks out of the box项目地址: https://gitcode.com/gh_mirrors/de/deployer
Provision Recipe 是 Deployer 提供的服务器自动化配置方案,用于将一台全新的 Ubuntu 服务器一键配置成可直接运行 PHP 应用的生产环境——包括系统更新、PHP/FPM、Node.js、数据库、Caddy Web 服务器、防火墙、SSH 安全策略与部署用户等全部环节。本文以 docs/recipe/provision.md 为骨架,结合 recipe/provision.php 及各子任务源码,完整讲解该 Recipe 的配置参数、15 个编排任务的作用与执行顺序、以及每个子模块(PHP、数据库、Node.js、用户、网站)的实现细节,让你既能读懂源码,也能直接上手将一台新服务器纳入 Deployer 管理。
一、Recipe 概览与引入方式
Provision Recipe 不是一个独立的单一文件,而是由 5 个子 Recipe 组合而成的总入口。在 Deployer 部署脚本(deploy.php)中通过以下方式引入:
require 'recipe/provision.php';引入后,recipe/provision.php 会自动再引入 5 个子模块:
| 子 Recipe | 文件 | 负责内容 |
|---|---|---|
| Databases | recipe/provision/databases.php | MySQL / MariaDB / PostgreSQL 的安装与建库建用户 |
| Nodejs | recipe/provision/nodejs.php | 通过 fnm 安装 Node.js(默认 LTS 版本) |
| Php | recipe/provision/php.php | PHP 与 PHP-FPM 的安装与调优、Composer |
| User | recipe/provision/user.php | 创建部署用户deployer、配置 sudo 与 SSH |
| Website | recipe/provision/website.php | Caddy 服务器配置与网站站点落盘 |
此外源码中还有一行add('recipes', ['provision']);(recipe/provision.php),将provision注册进 Deployer 的配方列表,便于dep recipe:list之类的命令检索。
二、配置参数(Configuration)
Provision Recipe 定义了两个顶层配置项,其余大量参数(如php_version、db_type、domain等)由各子 Recipe 定义。它们共同构成了整个配置流程所需的全部输入。
2.1 lsb_release
// Name of lsb_release like: focal, bionic, etc. // As only Ubuntu 20.04 LTS is supported for provision should be the `focal`. set('lsb_release', function () { return run("lsb_release -s -c"); });- 含义:返回远端服务器的 LSB 发行版代号,例如
focal(Ubuntu 20.04)、bionic(Ubuntu 18.04)。 - 默认值:
run("lsb_release -s -c"),即动态执行远端命令获取。 - 适用前提:文档明确说明 Provision 仅官方支持 Ubuntu 20.04 LTS,因此该值应为
focal。它用于在后续流程中判断系统环境与选择匹配的软件源。 - 源码位置:recipe/provision.php。
2.2 provision_user
// Default user to use for provisioning. set('provision_user', 'root');- 含义:执行整个 provisioning 流程时使用的远程账号。
- 默认值:
'root'。 - 作用:所有
provision:*任务在开始处都会执行set('remote_user', get('provision_user')),将当前任务的执行用户切换到该账号(通常是root),以保证有权限安装软件包、修改系统配置。 - 源码位置:recipe/provision.php。
2.3 子 Recipe 中的交互式参数
这些参数大多不是写死在配置里,而是在访问时通过交互式提问自动收集(Deployer 的ask系列函数),保证一台新服务器在无任何预配置的情况下也能完成初始化。全部参数如下:
| 参数 | 默认值 / 交互提示 | 定义位置 |
|---|---|---|
sudo_password | askHiddenResponse(' Password for sudo: '),密码隐藏输入 | recipe/provision/user.php |
domain | ask(' Domain: ', get('hostname')),默认取主机名 | recipe/provision/website.php |
public_path | ask(' Public path: ', 'public') | recipe/provision/website.php |
php_version | 自动探测composer.json中的require.php并归一化(如^8.2→8.2),探测失败默认8.4;可选5.6/7.4/8.2/8.3/8.4/8.5 | recipe/provision/php.php |
db_type | askChoice(' What DB to install? ', ['none','mysql','mariadb','postgresql'], 0),默认none(第 3 个参数为默认选项下标 0) | recipe/provision/databases.php |
db_name | ask(' DB name: ', 'prod') | recipe/provision/databases.php |
db_user | ask(' DB user: ', 'deployer') | recipe/provision/databases.php |
db_password | askHiddenResponse(' DB password: ') | recipe/provision/databases.php |
node_version | '--lts',即安装 Node.js 长期支持版 | recipe/provision/nodejs.php |
关键机制——provision:configure的配置回显:当某些参数未被你在部署脚本中显式设置(即Context::get()->getConfig()->hasOwn($name)为 false)时,provision:configure会逐个调用get($name)触发交互式提问,并在结束时把收集到的值以host(...)配置代码的形式打印出来(见 recipe/provision.php),形如:
====== Configuration Start ====== host('{{alias}}') ->set('sudo_password', 'xxx') ->set('domain', 'example.com') ->set('public_path', 'public') ->set('php_version', '8.4') ->set('db_type', 'mysql') ->set('db_user', 'deployer') ->set('db_name', 'prod') ->set('db_password', 'xxx'); ====== Configuration End ======这是设计上的一等公民用法:首次 provisioning 采用交互问答,之后把回显的代码固化进deploy.php,后续即可全自动重跑。注意sudo_password、db_password属于敏感信息,回显时请谨慎处理(实际生产中建议改用环境变量或密钥管理)。
三、核心任务:provision 编排与 15 个执行步骤
provision是 Recipe 的入口任务,源码 recipe/provision.php 中它被定义为一个分组任务(group task),按严格顺序依次执行 15 个子任务:
provision:check → provision:configure → provision:update → provision:upgrade → provision:install → provision:ssh → provision:firewall → provision:user → provision:php → provision:node → provision:databases → provision:composer → provision:server → provision:website → provision:verify运行方式:dep provision(会应用到当前 host 配置中的所有主机)。
下面按执行顺序逐个解析每个子任务的目的与实现。
3.1 provision:check —— 检查前置系统状态
源码:recipe/provision.php
任务首先将remote_user切到provision_user,然后读取/etc/os-release并解析出NAME与VERSION_ID:
- 仅支持 Ubuntu:若非 Ubuntu,打印醒目的警告横幅并默认拒绝继续(
askConfirmation('Do you want to continue? (Not recommended)', false),默认否);用户坚持继续才会放行,否则抛出RuntimeException。 - 版本约束:
version_compare($version, '20', '<'),即只接受 Ubuntu 20 及以上版本,低于 20 同样需要用户二次确认。 - 任务标记了
->oncePerNode():同一节点(主机)在一次执行周期内只运行一次,避免多主机并发时的重复检查。
3.2 provision:configure —— 收集必需参数
源码:recipe/provision.php
依次确保以下参数可用:sudo_password、domain、public_path、php_version、db_type;若db_type !== 'none',再额外确保db_user、db_name、db_password。凡是未显式配置的参数都会触发交互式提问,并把完整配置代码回显给用户(见上文 2.3 节)。该任务不标记oncePerNode,但通常由provision编排顺序保证只执行一次。
3.3 provision:update —— 添加软件源并更新
源码:recipe/provision.php
按顺序执行:
apt-get update(全程设置DEBIAN_FRONTEND=noninteractive避免交互卡死);- 安装前置工具
curl gpg software-properties-common; - 添加 PHP PPA:
apt-add-repository ppa:ondrej/php -y(设置LC_ALL=C.UTF-8防止 locale 问题); - 添加Caddy Web 服务器官方稳定源:通过 Cloudsmith 下载 GPG 密钥(去装甲后存入
/usr/share/keyrings/caddy-stable-archive-keyring.gpg),并把仓库描述写入/etc/apt/sources.list.d/caddy-stable.list; - 再次
apt-get update刷新索引。
任务标记->oncePerNode()与->verbose()(输出详细日志)。
3.4 provision:upgrade —— 升级全部软件包
源码:recipe/provision.php
执行apt-get upgrade -y,并设置900 秒超时(软件包升级可能较慢),同样使用非交互前端、oncePerNode与 verbose。
3.5 provision:install —— 安装系统级软件包
源码:recipe/provision.php
一次性安装 24 个基础包,涵盖:
- Web/服务:
caddy、sendmail、redis - 构建工具链:
build-essential、gcc、make、pkg-config、libpcre3-dev、libsqlite3-dev、libmcrypt4 - 运维/安全:
fail2ban、ufw(防火墙)、whois(提供mkpasswd)、ncdu(磁盘分析) - 通用工具:
acl、apt-transport-https、curl、git、unzip、uuid-runtime、debian-keyring、debian-archive-keyring、python-is-python3、sqlite3、nodejs
其中nodejs只是系统基础包,真正的 Node 版本管理由后面的provision:node(fnm)负责;whois包中的mkpasswd会在provision:user中用于生成密码哈希。任务同样有 900 秒超时、oncePerNode与 verbose。
3.6 provision:ssh —— 配置 SSH 安全策略
源码:recipe/provision.php
sed -i 's/PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config——禁用密码登录,强制密钥认证;ssh-keygen -A—— 生成缺失的主机密钥;service ssh restart—— 重启 SSH 服务生效;- 若
/root/.ssh不存在则创建并初始化authorized_keys。
⚠️安全提示:禁用密码登录前请务必确保你的公钥已部署(可先用dep provision:ssh_copy_id或手动写入authorized_keys),否则可能把自己锁在服务器外。
3.7 provision:firewall —— 配置防火墙
源码:recipe/provision.php
使用 UFW:
ufw allow 22 # SSH ufw allow 80 # HTTP ufw allow 443 # HTTPS ufw --force enable--force保证无交互直接启用。默认只放行 22/80/443 三个端口,其余全部拒绝,符合 Web 服务器的最小暴露面原则。
3.8 provision:user —— 创建 deployer 部署用户
源码:recipe/provision/user.php
这是从"root 一次性配置"切换到"日常部署用户"的关键步骤:
- 若
id deployer已存在,直接提示deployer user already exist并跳过(源码中留有 TODO 注释,表明后续版本可能补充对既有用户的校验与密码同步)。 - 否则依次执行:
useradd deployer创建用户;- 创建
/home/deployer/.ssh与/home/deployer/.deployer; adduser deployer sudo加入 sudo 组;chsh -s /bin/bash deployer设置默认 shell;- 从 root 复制
.profile、.bashrc,并开启彩色提示符(force_color_prompt=yes); - 用
mkpasswd -m sha-512将sudo_password生成 SHA-512 哈希,通过usermod --password设置密码(使用 secrets 机制,避免密码明文出现在命令输出中); - 把 root 的
authorized_keys复制给 deployer(继承密钥登录能力),并为 deployer 生成 ed25519 密钥对; - 设置目录/文件权限(
chown deployer:deployer、.ssh700、私钥与authorized_keys600 等,权限变更的异常会被捕获并降级为 warning); - 将 deployer 追加到
www-data与caddy组,使其能读写 Web 相关资源。
3.9 provision:ssh_copy_id —— 复制公钥到远端
源码:recipe/provision/user.php
独立于主流程的辅助任务(不包含在provision编排内,可单独运行dep provision:ssh_copy_id)。逻辑:
- 依次探测本机
~/.ssh/id_rsa.pub、id_ed25519.pub、id_ecdsa.pub、id_dsa.pub(通过parse_home_dir解析家目录); - 都没找到则提示手动粘贴公钥;
- 若仍为空则跳过并提示;
- 否则通过环境变量
PUBLIC_KEY追加写入/home/deployer/.ssh/authorized_keys(避免引号/特殊字符转义问题)。
3.10 provision:php —— 安装并调优 PHP / PHP-FPM
源码:recipe/provision/php.php
根据php_version(如8.4)安装 16 个扩展包:bcmath、cli、curl、dev、fpm、gd、imap、intl、mbstring、mysql、pgsql、readline、soap、sqlite3、xml、zip,一条命令安装完毕。
随后通过sed对 CLI 与 FPM 两份php.ini以及 FPM 池配置pool.d/www.conf做调优:
| 配置项 | 设置值 | 适用文件 |
|---|---|---|
error_reporting | E_ALL | CLI / FPM 的php.ini |
display_errors | On | CLI / FPM 的php.ini |
memory_limit | 512M | CLI / FPM 的php.ini |
upload_max_filesize | 128M | CLI / FPM 的php.ini |
date.timezone | UTC | CLI / FPM 的php.ini |
cgi.fix_pathinfo | 0 | FPM 的php.ini |
request_terminate_timeout | 60 | FPM 的www.conf |
catch_workers_output | yes | FPM 的www.conf |
php_flag[display_errors] | yes | FPM 的www.conf |
php_admin_value[error_log] | /var/log/fpm-php.www.log | FPM 的www.conf |
php_admin_flag[log_errors] | on | FPM 的www.conf |
最后对会话目录/var/lib/php/sessions设置733权限并加上 sticky bit(+t),确保 PHP-FPM 与 CLI 都能写入 session。任务标记->limit(1)(限制并发为 1,避免多主机并行冲突)与 verbose。
该子模块还附带两个日志查看任务:
logs:php-fpm(recipe/provision/php.php):自动列出/var/log下含fpm的日志文件并tail -f;无匹配时抛出异常。provision:composer(recipe/provision/php.php):从 getcomposer.org 下载安装脚本并用 PHP 执行,将composer.phar移到/usr/local/bin/composer,全局可用,oncePerNode。
3.11 provision:node —— 通过 fnm 安装 Node.js
源码:recipe/provision/nodejs.php
- 若检测到旧参数
nodejs_version被设置,直接抛出异常提示改用node_version。 - 通过
uname -m探测 CPU 架构,从 fnm(Fast Node Manager)GitHub Releases 下载对应二进制:arm/armv7*→fnm-arm32,aarch*/armv8*→fnm-arm64,其余 →fnm-linux。 - 解压到
/tmp,安装到/usr/local/bin/fnm,执行fnm install {{node_version}}(默认--lts即 LTS 版)。 - 将
eval "fnm env"写入/etc/profile.d/fnm.sh,使所有登录 shell 自动加载 fnm 环境。
3.12 provision:databases —— 安装并初始化数据库
源码:recipe/provision/databases.php
入口任务provision:databases(L27-L37)读取db_type,若为none直接跳过;否则invoke('provision:' . $dbType)动态分发到对应子任务,并标记->limit(1)。
三个数据库子任务细节:
provision:mysql(L39-L48)
apt-get install -y mysql-server(900 秒超时);- 以 root 免密方式创建用户
{{db_user}}(同时允许0.0.0.0与%来源),密码通过secrets 参数注入%db_password%,避免出现在命令回显中; - 授予
*.*全部权限(含GRANT OPTION),FLUSH PRIVILEGES; - 创建数据库
{{db_name}},字符集UTF8mb4、排序规则utf8mb4_bin。
provision:mariadb(L50-L59)
- 与 MySQL 流程完全一致,仅安装包改为
mariadb-server。
provision:postgresql(L61-L66)
- 安装
postgresql postgresql-contrib; - 以
sudo -u postgres psql依次执行:CREATE DATABASE {{db_name}};、CREATE USER {{db_user}} WITH ENCRYPTED PASSWORD '...';、GRANT ALL PRIVILEGES ON DATABASE {{db_name}} TO {{db_user}};。
3.13 provision:server —— 配置服务器基础目录
源码:recipe/provision/website.php
- 将
caddy用户加入www-data组; - 创建
/var/deployer目录,并把仓库内置的 404.html 写入其中,作为全局 404 兜底页面。
3.14 provision:website —— 生成并激活 Caddy 站点
源码:recipe/provision/website.php
这是把应用真正"挂"上 Web 服务器的步骤:
- 通过
become('deployer')临时切换到 deployer 用户(执行完由$restoreBecome()还原); - 创建部署目录
{{deploy_path}}并chown deployer:deployer,随后set('deploy_path', realpath(...))归一化路径; - 创建
log目录并chgrp caddy log; - 用模板引擎解析内置的 Caddyfile 模板(其中的
{{domain}}、{{deploy_path}}、{{public_path}}、{{php_version}}会被替换成实际值); - 智能合并策略:若站点目录已存在 Caddyfile,则生成
Caddyfile.new并与旧文件做diff;无差异则删除新文件,有差异则打印 diff 并让用户选择保留old还是new(askChoice默认保留旧版);不存在则直接写入; - 回到 root 身份,若
/etc/caddy/Caddyfile尚未import {{deploy_path}}/Caddyfile则追加该行,随后service caddy reload热加载; - 输出
Website {{domain}} configured!。
模板 recipe/provision/Caddyfile 的核心内容如下,它同时定义了反向代理、FastCGI 与访问日志:
{{domain}} { root * {{deploy_path}}/current/{{public_path}} encode zstd gzip file_server php_fastcgi * unix//run/php/php{{php_version}}-fpm.sock { resolve_root_symlink } log { output file {{deploy_path}}/log/access.log { mode 0644 } } handle_errors { @404 { expression {http.error.status_code} == 404 } rewrite @404 /404.html encode zstd gzip file_server { root /var/deployer } } }要点解读:
root指向{{deploy_path}}/current/{{public_path}}——这与 Deployer 的"符号链接 release 机制"天然契合,current即当前版本软链接,配合resolve_root_symlink让 PHP-FPM 正确解析真实路径;encode zstd gzip启用 zstd 与 gzip 双重压缩;php_fastcgi直连php{{php_version}}-fpm.sock(Unix socket),即前面安装的 FPM 版本;- 访问日志写入
{{deploy_path}}/log/access.log; - 404 请求被重写到
/var/deployer/404.html的统一错误页。
3.15 provision:verify —— 验证 provisioning 是否成功
源码:recipe/provision.php
使用fetch('{{domain}}', 'get', [], null, $info, true)对配置好的域名发起 HTTP GET 请求,并检查返回的$info['http_code']:由于此时尚未部署任何应用(current目录还不存在),预期返回404,命中即输出provisioned successfully!。
验证逻辑的合理之处:404 说明 Caddy 已正常监听该域名并返回了内置错误页(而非连接失败/超时/500),恰好证明"Web 服务器就绪、只等部署代码"这一中间态,与"部署应用后再验证 200"的场景形成闭环。
3.16 辅助日志任务
除编排任务外,Provision 还提供了两个实时日志任务:
logs:access(recipe/provision/website.php):tail -f {{deploy_path}}/log/access.log跟踪站点访问日志;logs:caddy(recipe/provision/website.php):sudo journalctl -u caddy -f跟踪 Caddy 服务日志。
四、推荐使用流程
把 Provision Recipe 纳入 Deployer 工作流的推荐路径:
- 准备:一台全新的 Ubuntu 20.04(或更新版本)服务器,确保能从本机免密 SSH 登录 root;在部署脚本中引入
require 'recipe/provision.php';并定义 host。 - 预置公钥(可选但强烈推荐):先执行
dep provision:ssh_copy_id把本机公钥写入远端。 - 执行配置:
dep provision,按提示回答 sudo 密码、域名、公开目录、PHP 版本、数据库类型/名称/用户/密码等提问。 - 固化配置:把
provision:configure回显的host(...)->set(...)代码写入deploy.php,此后即可无交互重跑;或直接为所有参数提供显式配置,例如:
host('example.com') ->set('provision_user', 'root') ->set('domain', 'example.com') ->set('public_path', 'public') ->set('php_version', '8.4') ->set('db_type', 'mysql') ->set('db_user', 'deployer') ->set('db_name', 'prod') ->set('db_password', 'your-strong-password') ->set('sudo_password', 'your-sudo-password') ->set('deploy_path', '/var/www/example.com');- 部署应用:provision 完成后,即可用常规的
dep deploy流程发布代码(current符号链接由 Caddyfile 的resolve_root_symlink平滑衔接)。 - 日常运维:使用
logs:access、logs:caddy、logs:php-fpm观察运行日志。
五、重要限制与注意事项
- 操作系统强约束:源码在 provision:check 中明确只支持 Ubuntu(且建议 20+);文档声明 "As only Ubuntu 20.04 LTS is supported for provision",其他发行版会收到警告并需人工确认才能继续,非 Ubuntu 上不建议执行。
- 全程以 root 运行:
provision_user默认root,请保证 root 的 SSH 公钥已部署;provision:ssh会禁用 SSH 密码登录,务必先确认密钥可用。 - 交互式提问不可避免:首次运行时多数参数(
sudo_password、domain、db_password等)通过ask/askHiddenResponse/askChoice收集,自动化 CI 场景请务必预先在脚本中->set()全部参数(可参考provision:configure的回显代码)。 - 敏感信息处理:数据库密码与 sudo 密码通过 Deployer 的
secrets参数传入远端命令,避免明文出现在进程列表中;回显的配置代码含明文密码,注意保管deploy.php。 - 并发限制:
provision:php、provision:databases、provision:website标记了->limit(1),多数任务标记->oncePerNode(),多主机场景下请理解这些约束以避免资源竞争。 - 不可逆操作:
provision:upgrade会升级系统全部软件包(900 秒超时),provision:firewall会立即启用 UFW;执行前请评估对现有环境的影响。
六、小结
Provision Recipe 是 Deployer 体系中"从零到可部署"的自动化方案:provision一个命令即可串起系统检查、软件源、系统升级、基础软件、SSH 加固、防火墙、部署用户、PHP 与 FPM、Node.js、数据库、Caddy 站点直至最终验证的完整链路,并且通过与current符号链接机制和resolve_root_symlink的配合,与 Deployer 常规部署流程无缝衔接。理解 recipe/provision.php 及其 5 个子模块的实现,既能让你安全地把它用在新服务器初始化上,也能为你按需裁剪或扩展属于自己的 provisioning 流程提供参考。
【免费下载链接】deployerThe PHP deployment tool with support for popular frameworks out of the box项目地址: https://gitcode.com/gh_mirrors/de/deployer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考