本文目录导读:

为PHP项目编写部署脚本需要考虑环境一致性、安全性和可重复性,下面提供一个从基础到进阶的部署方案,涵盖Shell脚本、Ansible和Docker三种主流方式。
基础Shell部署脚本
一个完整的deploy.sh示例:
#!/bin/bash
set -e # 任何命令失败立即退出
set -u # 使用未定义变量时报错
# ============ 配置区域 ============
PROJECT_NAME="my-php-app"
REPO_URL="https://github.com/your/repo.git"
DEPLOY_DIR="/var/www/${PROJECT_NAME}"
BACKUP_DIR="/var/backups/${PROJECT_NAME}"
BRANCH="main"
PHP_VERSION="8.1"
COMPOSER_PATH="/usr/local/bin/composer"
# ================================
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始部署 ${PROJECT_NAME}..."
# 1. 创建目录结构
mkdir -p "${DEPLOY_DIR}" "${BACKUP_DIR}"
# 2. 备份当前版本(如果存在)
if [ -d "${DEPLOY_DIR}/current" ]; then
echo "备份当前版本..."
TIMESTAMP=$(date +'%Y%m%d_%H%M%S')
cp -r "${DEPLOY_DIR}/current" "${BACKUP_DIR}/backup_${TIMESTAMP}"
# 只保留最近5个备份
ls -t "${BACKUP_DIR}" | tail -n +6 | xargs -I {} rm -rf "${BACKUP_DIR}/{}" 2>/dev/null
fi
# 3. 克隆/拉取代码
if [ -d "${DEPLOY_DIR}/current" ]; then
echo "拉取最新代码..."
cd "${DEPLOY_DIR}/current"
git fetch origin
git reset --hard "origin/${BRANCH}"
else
echo "首次克隆代码..."
git clone --depth=1 -b "${BRANCH}" "${REPO_URL}" "${DEPLOY_DIR}/current"
fi
# 4. 设置文件权限
echo "设置文件权限..."
find "${DEPLOY_DIR}/current" -type f -exec chmod 644 {} \;
find "${DEPLOY_DIR}/current" -type d -exec chmod 755 {} \;
# Web服务器用户(如www-data)可写目录
chmod -R 775 "${DEPLOY_DIR}/current/storage"
chmod -R 775 "${DEPLOY_DIR}/current/bootstrap/cache"
chown -R www-data:www-data "${DEPLOY_DIR}/current"
# 5. 安装Composer依赖
echo "安装PHP依赖..."
cd "${DEPLOY_DIR}/current"
${COMPOSER_PATH} install --no-dev --optimize-autoloader --no-interaction
# 6. 环境配置
echo "配置环境..."
if [ ! -f ".env" ]; then
cp .env.example .env
# 生成APP_KEY(适用于Laravel等框架)
php artisan key:generate --force
fi
# 7. 数据库迁移(需要数据库连接)
# php artisan migrate --force
# 8. 清理缓存(Laravel示例)
echo "优化缓存..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
# 9. 创建符号链接(部署版本管理)
ln -sfn "${DEPLOY_DIR}/current" "${DEPLOY_DIR}/latest"
# 10. 重启PHP-FPM(如果使用)
echo "重启PHP-FPM..."
sudo systemctl reload php${PHP_VERSION}-fpm
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 部署完成!"
使用方法:
chmod +x deploy.sh ./deploy.sh
零停机部署脚本(使用符号链接)
#!/bin/bash
set -e
DEPLOY_DIR="/var/www/my-app"
RELEASE_DIR="${DEPLOY_DIR}/releases/$(date +'%Y%m%d%H%M%S')"
SHARED_DIR="${DEPLOY_DIR}/shared"
echo "创建新版本目录..."
mkdir -p "${RELEASE_DIR}"
echo "克隆指定版本代码..."
git clone --depth=1 -b main https://github.com/your/repo.git "${RELEASE_DIR}"
echo "共享资源链接..."
ln -nfs "${SHARED_DIR}/.env" "${RELEASE_DIR}/.env"
ln -nfs "${SHARED_DIR}/storage" "${RELEASE_DIR}/storage"
echo "安装依赖..."
cd "${RELEASE_DIR}"
composer install --no-dev --optimize-autoloader
echo "切换符号链接(零停机)..."
ln -nfs "${RELEASE_DIR}" "${DEPLOY_DIR}/current"
echo "重启服务..."
sudo systemctl reload nginx
sudo systemctl reload php8.1-fpm
echo "清理旧版本(保留最近3个)..."
cd "${DEPLOY_DIR}/releases"
ls -t | tail -n +4 | xargs -r rm -rf
目录结构:
/var/www/my-app/
├── current -> releases/20231001_120000/
├── releases/
│ ├── 20231001_110000/
│ ├── 20231001_120000/
│ └── 20231002_090000/
└── shared/
├── .env
└── storage/
Docker部署脚本
1 Dockerfile
FROM php:8.1-fpm-alpine
# 安装系统依赖
RUN apk add --no-cache \
git \
unzip \
libpng-dev \
libzip-dev \
oniguruma-dev \
&& docker-php-ext-install pdo_mysql mbstring zip gd
# 安装Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 设置工作目录
WORKDIR /var/www/html
# 复制项目文件
COPY . .
# 安装PHP依赖
RUN composer install --no-dev --optimize-autoloader --no-interaction
# 权限设置
RUN chown -R www-data:www-data storage bootstrap/cache
2 docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: php-app
restart: unless-stopped
volumes:
- ./storage:/var/www/html/storage
environment:
- DB_HOST=mysql
- DB_DATABASE=app
- DB_USERNAME=app
- DB_PASSWORD=secret
depends_on:
- mysql
- redis
networks:
- app-network
web:
image: nginx:alpine
container_name: nginx-web
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- .:/var/www/html
depends_on:
- app
networks:
- app-network
mysql:
image: mysql:8.0
container_name: mysql-db
restart: unless-stopped
environment:
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: secret
MYSQL_ROOT_PASSWORD: rootsecret
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-network
redis:
image: redis:alpine
container_name: redis-cache
restart: unless-stopped
networks:
- app-network
volumes:
mysql_data:
networks:
app-network:
driver: bridge
3 deploy-docker.sh
#!/bin/bash set -e echo "===== Docker部署开始 =====" # 1. 拉取最新代码 git pull origin main # 2. 构建新镜像 docker-compose build --no-cache app # 3. 执行数据库迁移(如果有) docker-compose run --rm app php artisan migrate --force # 4. 启动服务(零停机滚动更新) docker-compose up -d --scale app=3 --no-recreate web # 5. 等待健康检查 sleep 10 docker-compose ps echo "===== 部署完成 ====="
Ansible自动化部署
deploy-php.yml:
---
- name: 部署PHP应用
hosts: webservers
become: yes
vars:
project_name: "my-php-app"
repo_url: "https://github.com/your/repo.git"
deploy_dir: "/var/www/{{ project_name }}"
branch: "main"
tasks:
- name: 安装必要系统包
apt:
name:
- git
- nginx
- php8.1-fpm
- php8.1-mysql
- php8.1-xml
- php8.1-mbstring
- composer
state: present
update_cache: yes
- name: 创建部署目录
file:
path: "{{ deploy_dir }}"
state: directory
owner: www-data
group: www-data
mode: 0755
- name: 克隆代码
git:
repo: "{{ repo_url }}"
dest: "{{ deploy_dir }}/current"
version: "{{ branch }}"
force: yes
- name: 设置文件权限
file:
path: "{{ deploy_dir }}/current"
state: directory
recurse: yes
owner: www-data
group: www-data
- name: 配置.env
copy:
src: "{{ playbook_dir }}/files/.env.production"
dest: "{{ deploy_dir }}/current/.env"
owner: www-data
group: www-data
mode: 0600
- name: 安装Composer依赖
composer:
command: install
working_dir: "{{ deploy_dir }}/current"
no_dev: yes
optimize_autoloader: yes
- name: 执行数据库迁移
command:
cmd: php artisan migrate --force
chdir: "{{ deploy_dir }}/current"
- name: 优化缓存
command:
cmd: "{{ item }}"
chdir: "{{ deploy_dir }}/current"
loop:
- php artisan config:cache
- php artisan route:cache
- php artisan view:cache
- name: 配置Nginx
template:
src: "nginx.conf.j2"
dest: "/etc/nginx/sites-available/{{ project_name }}"
notify: reload nginx
- name: 启用站点
file:
src: "/etc/nginx/sites-available/{{ project_name }}"
dest: "/etc/nginx/sites-enabled/{{ project_name }}"
state: link
- name: 确保PHP-FPM运行
systemd:
name: php8.1-fpm
state: started
enabled: yes
handlers:
- name: reload nginx
systemd:
name: nginx
state: reloaded
运行Ansible:
ansible-playbook -i inventory.ini deploy-php.yml
CI/CD集成示例(GitHub Actions)
.github/workflows/deploy.yml:
name: Deploy PHP Application
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, xml, gd, zip
tools: composer
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Run tests
run: vendor/bin/phpunit
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-app
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
sudo systemctl reload php8.1-fpm
安全注意事项
- 不要在脚本中硬编码数据库密码,使用环境变量或密钥管理工具
- 限制部署用户权限(尽量不用root)
- 备份前的版本,确保可以快速回滚
性能优化
- 使用
--optimize-autoloader生成优化的类映射 - 预编译模板和配置文件
- 使用OPcache
监控告警
- 部署后在脚本中添加健康检查
- 监控核心页面响应时间
- 设置回滚触发条件
选择哪种方式取决于你的项目复杂度:
- 简单项目:使用Shell脚本即可
- 微服务架构:推荐Docker
- 多服务器环境:使用Ansible等配置管理工具
- 持续集成:结合CI/CD工具
建议先在测试环境验证脚本,确保回滚机制正常工作后再用于生产环境。