Skip to content

Latest commit

 

History

History
285 lines (199 loc) · 13.8 KB

File metadata and controls

285 lines (199 loc) · 13.8 KB

GEOFlow Laravel 生产 Docker 部署

本文对应仓库中的生产编排文件:

  • docker-compose.prod.yml
  • docker/Dockerfile.prod
  • docker/nginx/Dockerfile.prod
  • docker/nginx/default.conf
  • docker/entrypoint.prod.sh
  • .env.prod.example

1. 方案说明

生产环境推荐使用:

  • web: nginx
  • app: php-fpm
  • queue: php artisan queue:work
  • scheduler: php artisan schedule:work
  • reverb: php artisan reverb:start
  • postgres: PostgreSQL 16 + pgvector
  • redis: Redis 7

这套方案与当前开发用 docker-compose.yml 分离:

  • 开发:docker-compose.yml,继续使用 php artisan serve
  • 生产:docker-compose.prod.yml,改为 nginx + php-fpm

1.1 一键部署脚本

一键部署脚本仅用于全新、空数据库安装。如果希望在常见云服务器、VPS 或面板服务器上先做环境自检,再自动完成首次生产 Docker 部署,可以使用仓库中的参考脚本:

curl -fsSL https://raw.githubusercontent.com/yaojingang/GEOFlow/main/deploy-scripts/geoflow-docker-deploy.sh -o geoflow-docker-deploy.sh
bash geoflow-docker-deploy.sh

脚本会完成:

  • 检查 CPU、内存、磁盘、Docker、Docker Compose 与端口占用
  • 克隆或更新 GEOFlow 源码
  • 生成 .env.prod 并写入生产默认配置
  • 启动 PostgreSQL、Redis、Nginx、PHP-FPM、队列、调度和 Reverb
  • 执行迁移、写入默认管理员、清理并重建 Laravel 缓存
  • 调用 deploy-scripts/geoflow-healthcheck.sh 做部署后自检

如需部署成功后删除临时脚本,可使用:

GEOFLOW_SELF_DELETE=1 bash geoflow-docker-deploy.sh

完整变量说明见 deploy-scripts/README.md

已有数据的实例禁止使用一键脚本升级,也禁止滚动升级。请完整执行 3.1 节的停机排空协议。

2. 准备环境文件

cp .env.prod.example .env.prod
vi .env.prod

至少确认这些值:

APP_URL=https://your-domain.com
TRUSTED_PROXIES=*
APP_KEY=base64:replace-with-generated-key

DB_DATABASE=geo_flow
DB_USERNAME=geo_user
DB_PASSWORD=change-this-password

REDIS_PASSWORD=
WEB_PORT=18080
REVERB_EXPOSE_PORT=18081

说明:

  • APP_KEY 可留空:应用容器启动时会 key:generate 写回 .env.prod(可写挂载);也可在宿主机执行 php artisan key:generate --show 后粘贴。
  • TRUSTED_PROXIES 用于反向代理、CDN、负载均衡或一级目录部署。若外层代理会传 X-Forwarded-Proto / X-Forwarded-Host / X-Forwarded-Prefix,生产环境通常可设为 * 或具体代理 IP。
  • 如果部署在任意一级目录下,例如外部访问路径是 /wiki/docs/site,不要把目录写进 ADMIN_BASE_PATH;应由反向代理透传 X-Forwarded-Prefix,后台路径仍保持 ADMIN_BASE_PATH=geo_admin
  • AUTO_MIGRATE=true 由生产 init 服务执行迁移;常驻服务不接收 .env.prod 作为容器环境变量,重启时不会重复初始化。
  • AUTO_INSTALL_ONCE=true 由生产 init 服务在迁移后运行 php artisan geoflow:install;该命令只在空库首次安装时执行安装填充,旧库只补初始化标记。
  • 生产镜像不会在启动时执行 composer install
  • postgres / redis 凭据docker-compose.prod.yml 中 postgres 使用 DB_DATABASE / DB_USERNAME / DB_PASSWORD 映射为官方镜像的 POSTGRES_*;redis 使用 REDIS_PASSWORD;值均由 Compose 插值(推荐 --env-file .env.prod),与 Laravel 的 DB_* 同源、不重复定义。
  • 建议仍使用 --env-file .env.prod:便于插值 WEB_PORTPOSTGRES_DATA_DIR 等与根目录 .env 对齐;若曾用错误密码初始化过 Postgres,须删掉 POSTGRES_DATA_DIR 对应数据目录后再启动。

3. 启动步骤

下文统一使用前缀(请原样复制):

export COMPOSE_PROD='docker compose --env-file .env.prod -f docker-compose.prod.yml'

首次部署建议按以下顺序:

$COMPOSE_PROD build
$COMPOSE_PROD up -d postgres redis
$COMPOSE_PROD up -d init
$COMPOSE_PROD up -d app web queue scheduler reverb

init 服务会把 GEOFLOW_SECURITY_FRESH_INSTALL_CONFIRMED=true 仅注入该一次性容器。迁移只在单一 fresh migration batch 且业务表为空时接受此标志;已有部署仍需下一节的 drain confirmation。

3.1 受管图片删除升级门禁

升级到包含 images.managed_path_hash 的版本时,先保持 GEOFLOW_MANAGED_IMAGE_DELETION_ENABLED=false。已有数据或既有迁移历史的数据库必须使用 down → stop/drain → one-time confirmation → migrate → start-new → readiness → up → enable 的顺序。迁移会在任何 schema 变更前检查 GEOFLOW_SECURITY_UPGRADE_DRAIN_CONFIRMED=true;未确认时会安全终止。全新空库使用 init 服务限定作用域的 GEOFLOW_SECURITY_FRESH_INSTALL_CONFIRMED=true

滚动升级、migration-first、一键升级均无法覆盖已经通过旧版空 replay 检查的在途请求,因此明确禁止用于已有数据的实例。一次性确认仅表示运维人员已经完成排空,不会自动停止进程。

# 1. 先进入维护模式,再停止入口和所有旧版常驻进程。
$COMPOSE_PROD exec app php artisan down
$COMPOSE_PROD stop web queue scheduler reverb

# 2. 等待负载均衡连接、PHP 请求、队列任务和调度任务全部结束;确认零在途后停止 app。
# 请使用平台连接数、进程列表和队列监控完成确认。
$COMPOSE_PROD stop app

# 3. 仅在确认全部旧进程和在途请求已排空后,临时修改 .env.prod:
# GEOFLOW_SECURITY_UPGRADE_DRAIN_CONFIRMED=true
# GEOFLOW_MANAGED_IMAGE_DELETION_ENABLED=false

# 4. 构建新镜像,并由一次性 init 服务执行新版本迁移。
$COMPOSE_PROD build
$COMPOSE_PROD up -d postgres redis
$COMPOSE_PROD up init

# 5. 迁移成功后立即将一次性确认恢复为 false,再启动全部新版本进程:
# GEOFLOW_SECURITY_UPGRADE_DRAIN_CONFIRMED=false
$COMPOSE_PROD up -d app web queue scheduler reverb

# 6. 回填并检查受管图片身份;remaining、terminal、registry_failed 必须都为 0。
$COMPOSE_PROD run --rm app php artisan geoflow:managed-images:readiness

# 7. 运行只读安全审计,并逐项处理或确认 finding。
$COMPOSE_PROD run --rm app php artisan geoflow:security-audit

# 8. 退出维护模式并恢复流量。
$COMPOSE_PROD exec app php artisan up

readiness 命令会回填已有图片路径哈希,并在路径锁内对账注册表、文件状态和内容哈希。永久无效的历史路径会保留稳定终态哈希,并计入 terminal;文件缺失、身份不一致或无法安全读取会计入 registry_failed。确认输出表格的 remainingterminalregistry_failed 都为 0,再运行 geoflow:security-audit。该审计命令严格只读,不回填哈希、不修改数据库、不访问 HTTP/DNS,也不启动外部进程。人工可读模式和 JSON 模式使用相同 finding 集合:

# 人工检查
$COMPOSE_PROD run --rm app php artisan geoflow:security-audit

# 自动化检查;JSON schema_version 固定为 1
$COMPOSE_PROD run --rm app php artisan geoflow:security-audit --json

退出码 0 表示没有 finding;退出码 1 表示发现问题、需要复核的私网出站例外,或审计无法安全完成。JSON 包含 schema_versionstatus、按 severity 汇总的 summary 和按稳定 code 排序的 findings,不会输出路径、URL、token、owner 或哈希原文。该命令用于 GEOFlow 运行数据与安全配置检查,依赖漏洞检查仍需单独执行 composer audit

完成审计处理,再次确认运行中的容器全部来自新镜像,然后将 GEOFLOW_MANAGED_IMAGE_DELETION_ENABLED=true 写入生产环境配置,并重新创建会执行图片清理的新版本进程:

$COMPOSE_PROD up -d --force-recreate app queue scheduler

门禁关闭或回填未完成时,数据库记录仍可删除,物理图片文件会安全保留并记录清理失败日志。

以下单条命令仅适用于全新空库安装。已有数据的升级执行它会触发安全迁移门禁;不要通过预设一次性确认绕过停机排空流程:

docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --build

但第一次部署仍建议先观察 init 是否完成迁移。

4. 访问方式

  • 前台与后台统一从 web(Nginx)进入
  • 站点:http://服务器IP:${WEB_PORT} 或你的反向代理域名
  • 后台:/geo_admin/login(或你的 ADMIN_BASE_PATH
  • Reverb:默认映射 ${REVERB_EXPOSE_PORT}:8080

默认管理员(首次安装)

生产 docker-compose.prod.ymlinit 服务会在迁移完成后执行 php artisan geoflow:install。该命令只在空库首次安装时写入默认管理员;如果检测到已有业务数据但没有安装标记,只会补写标记并跳过填充,避免重启、重构或拉取新代码后污染线上网站设置、广告、提示词、分类和文章。常驻的 appqueueschedulerreverb 服务不会自动 seed。

# 如果你没有使用 compose 的 init 服务,可在迁移成功后执行首次安装命令:
docker compose --env-file .env.prod -f docker-compose.prod.yml run --rm app php artisan geoflow:install

账号由 Database\Seeders\AdminUserSeeder 在首次空库安装时写入:只在目标用户名不存在时创建,重复执行不会覆盖已存在账号的用户名、邮箱或密码。前台演示分类和文章默认不会写入;只有显式设置 GEOFLOW_SEED_FRONTEND_DEMO=true 且首次空库安装时才会导入演示数据。

项目
用户名 GEOFLOW_ADMIN_USERNAME,默认 admin
密码 生产环境请设置 GEOFLOW_ADMIN_PASSWORD;若留空且账号尚不存在,首次安装会生成一次性随机密码并输出到初始化日志

登录地址:站点根 URL + /geo_admin/login(默认;若改过 ADMIN_BASE_PATH 则把 geo_admin 换成你的前缀)。账号已存在时,重复执行安装命令不会重新生成或打印密码。上线后请立即修改默认或初始化生成的密码。

初始化数据维护规则

后续新增默认站点配置、默认提示词、默认渠道、默认模板、演示分类或演示文章时,必须接入 php artisan geoflow:install 的首次空库安装路径,或通过明确的手动修复命令执行。不要把用户可修改的默认数据放到常规容器启动、迁移或每次升级都会自动执行的 seed 流程里,避免覆盖线上用户配置。

5. 关键差异

当前开发 Docker

  • php:8.4-cli-bookworm
  • php artisan serve
  • 允许运行时 composer install
  • 默认 AUTO_MIGRATE=true

当前生产 Docker

  • php:8.4-fpm-bookworm
  • nginx 直接服务静态文件,PHP 交给 php-fpm
  • 依赖在构建期安装完成
  • 通过 docker/entrypoint.prod.sh 执行可选的等待数据库、迁移、php artisan optimize

6. 运维建议

  • 不要对外暴露 postgres / redis
  • 建议在反向代理层只公开 80/443
  • 若更新了 PHP 代码,因 OPcache validate_timestamps=0,请重新构建镜像
  • 修改 .env.prod 后,执行:
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d

该命令仅用于没有代码、镜像或数据库迁移变化的配置重建。已有部署的代码更新统一执行 3.1 节的停机排空协议。

  • 全新空库需要手动跑迁移时,可把 fresh-install intent 限定在该一次性容器:
docker compose --env-file .env.prod -f docker-compose.prod.yml run --rm \
  -e GEOFLOW_SECURITY_FRESH_INSTALL_CONFIRMED=true \
  -e AUTO_MIGRATE=true \
  -e AUTO_OPTIMIZE=false \
  app php artisan about

或直接执行:

docker compose --env-file .env.prod -f docker-compose.prod.yml run --rm \
  -e GEOFLOW_SECURITY_FRESH_INSTALL_CONFIRMED=true \
  app php artisan migrate --force

已有部署不得使用上述 fresh-install 命令;手动迁移也必须先完成 3.1 节的 down、停止排空和 drain confirmation。

7. 回滚与更新

已有部署的更新统一执行 3.1 节。git pull 与镜像构建应放在停机排空流程内,禁止用 git pullbuildup -d 直接替代该流程。

回滚:

  • 切回目标 commit / tag
  • 先确认目标版本与当前数据库 schema 兼容
  • 使用与 3.1 节相同的停机排空边界重建并启动目标版本

8. 原理说明

  • 静态文件:由 web 容器中的 Nginx 直接返回
  • PHP 请求:Nginx 通过 FastCGI 转发给 app:9000
  • Laravel 代码执行:由 php-fpm 进程解析并运行 public/index.php

9. 构建时拉取基础镜像失败(not found / could not fetch content descriptor

若日志类似 FROM php:8.4-fpm-bookworm 或某层 application/vnd.oci.image.layer... from remote: not found,多为仓库侧或镜像加速与 manifest 不一致,而非项目 Dockerfile 写错。

建议按顺序尝试:

  1. 直接重试 docker compose --env-file .env.prod -f docker-compose.prod.yml build(偶发 Hub 或链路问题)。
  2. 单独拉基础镜像,确认是拉取问题还是仅 BuildKit 缓存问题:
    docker pull php:8.4-fpm-bookworm
    若此处同样 not found,说明当前访问的 registry/加速源缺层,需换源或直连。
  3. 检查本机 /etc/docker/daemon.jsonregistry-mirrors:部分公共加速源对 docker.io 层同步不完整,可暂时注释镜像加速后重启 Docker,再 docker pull / build;或换成你环境稳定可用的镜像源策略。
  4. 清理构建缓存后再构建
    docker builder prune -f
    必要时再 docker system prune(注意会删掉未使用镜像,执行前自行确认)。

仍失败时,把 docker pull php:8.4-fpm-bookworm 的完整输出daemon.json 中与 registry 相关的配置(可打码)一并排查网络与镜像源。