QiaoFu
技术 ·技术

从拉取代码到一键上线:QiaoFu Blog 的完整部署流程

记录 QiaoFu Blog 从代码拉取、环境配置和 Docker 镜像构建,到通过 PowerShell 脚本上传服务器并一键完成前后端部署的完整流程。

属于项目 QiaoFu Blog查看完整项目轨迹 →
从拉取代码到一键上线:QiaoFu Blog 的完整部署流程 封面

从拉取代码到一键上线:QiaoFu Blog 的完整部署流程

QiaoFu Blog 是一套前后端分离的个人内容平台:

  • 前端:Nuxt 4、Vue 3、TypeScript
  • 后端:Rust、Salvo、SQLx
  • 数据库:MySQL 8
  • 部署方式:Docker、Docker Compose、Nginx
  • 自动化脚本:PowerShell

这篇文章记录如何从零拉取代码、配置本地环境、构建 Docker 镜像,并通过 deploy.ps1 将前后端一次性发布到 Linux 服务器。

一、项目仓库

项目分为两个独立仓库。

前端:

Text
https://gitee.com/ainimr/blog-frontend

后端:

Text
https://gitee.com/ainimr/blog-backend

在 Windows 开发机上创建项目目录:

PowerShell
New-Item -ItemType Directory -Path QiaoFu
Set-Location QiaoFu

New-Item -ItemType Directory -Path frontend
New-Item -ItemType Directory -Path backend

分别拉取前后端代码:

PowerShell
git clone https://gitee.com/ainimr/blog-frontend.git frontend/blog-frontend
git clone https://gitee.com/ainimr/blog-backend.git backend/blog-backend

最终目录结构如下:

Text
QiaoFu/
├─ deploy.ps1
├─ frontend/
│  └─ blog-frontend/
│     ├─ Dockerfile
│     ├─ docker-compose.runtime.yml
│     ├─ scripts/
│     │  └─ package-image.ps1
│     └─ ...
└─ backend/
   └─ blog-backend/
      ├─ Dockerfile
      ├─ docker-compose.runtime.yml
      ├─ scripts/
      │  └─ package-image.ps1
      └─ ...

根目录的 deploy.ps1 会按照这个结构查找前后端项目,因此不要随意修改目录层级。

二、本地开发环境

如果只需要通过 Docker 构建并部署,开发机安装以下工具即可:

  • Git
  • Docker Desktop
  • PowerShell
  • OpenSSH Client

如果还需要脱离 Docker 运行源码,则另外安装:

  • Node.js 24
  • pnpm
  • Rust stable
  • MySQL 8

安装 Docker Desktop

Windows 推荐使用 WSL 2 后端。下载安装后启动 Docker Desktop,并确认正在运行 Linux 容器。具体要求和安装方式以 Docker Desktop Windows 官方文档为准。

安装完成后打开 PowerShell:

PowerShell
docker version
docker compose version
docker run --rm hello-world

三个命令都能正常执行,说明 Docker 环境已经可以使用。

Docker Desktop 默认支持构建 Linux 镜像,也支持通过模拟构建 linux/amd64linux/arm64 镜像。Docker 多平台构建文档

三、本地运行后端

进入后端目录:

PowerShell
Set-Location backend/blog-backend
Copy-Item .env.example .env

编辑 .env,至少填写数据库连接和 JWT 密钥:

ENV
BLOG__SERVER__HOST=127.0.0.1
BLOG__SERVER__PORT=5800

DATABASE_URL=mysql://blog:数据库密码@127.0.0.1:3306/blog

BLOG__JWT__SECRET=替换成足够长的随机字符串
BLOG__JWT__ISSUER=blog-backend
BLOG__JWT__AUDIENCE=blog-admin

BLOG__UPLOAD__DIRECTORY=storage/uploads
BLOG__GEOIP__DATABASE=storage/geoip/ip2region.xdb

BLOG__SMTP__ENABLED=false

数据库密码中如果包含 @:/ 等字符,需要先进行 URL 编码。

创建数据库:

SQL
CREATE DATABASE blog
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

CREATE USER 'blog'@'127.0.0.1'
  IDENTIFIED BY '替换成数据库密码';

GRANT ALL PRIVILEGES ON blog.* TO 'blog'@'127.0.0.1';

FLUSH PRIVILEGES;

启动后端:

PowerShell
cargo run

服务启动后会自动执行尚未应用的数据库迁移。

验证接口:

PowerShell
Invoke-RestMethod http://127.0.0.1:5800/api/health

首次运行还需要创建管理员:

PowerShell
cargo run --bin create_admin

四、本地运行前端

进入前端目录:

PowerShell
Set-Location frontend/blog-frontend
Copy-Item .env.example .env

编辑 .env

ENV
NUXT_PUBLIC_API_BASE=http://127.0.0.1:5800
NUXT_PUBLIC_SITE_URL=http://localhost:3000

安装依赖并启动:

PowerShell
corepack enable
pnpm install
pnpm dev

浏览器访问:

Text
http://localhost:3000

生产环境必须把 NUXT_PUBLIC_SITE_URL 改成最终的 HTTPS 域名,否则 canonical、RSS 和 Sitemap 会生成错误地址。

五、为什么在本地构建镜像

服务器只负责运行镜像,不负责安装 Node.js、pnpm、Rust 或 Cargo,也不在服务器上编译项目。

整体流程是:

Text
开发机拉取代码

Docker 构建 Linux 镜像

导出为 TAR 文件

上传服务器

服务器加载镜像

Docker Compose 重建容器

这样可以降低服务器内存压力,也能保证服务器运行的内容与本地构建产物一致。

六、分别构建前后端镜像

后端打包:

PowerShell
Set-Location backend/blog-backend

.\scripts\package-image.ps1 `
  -Version v1.0.0 `
  -Platform linux/amd64

前端打包:

PowerShell
Set-Location frontend/blog-frontend

.\scripts\package-image.ps1 `
  -Version v1.0.0 `
  -Platform linux/amd64

普通云服务器一般使用:

Text
linux/amd64

ARM 云服务器使用:

Text
linux/arm64

可以在服务器执行下面的命令确认架构:

Bash
uname -m

对应关系:

Text
x86_64  → linux/amd64
aarch64 → linux/arm64

打包脚本会完成以下工作:

  1. 根据目标平台构建 Docker 镜像。
  2. 检查镜像的系统和 CPU 架构。
  3. 使用 docker save 导出镜像。
  4. 将镜像、Compose 文件和配置示例打包。
  5. 计算发布包的 SHA256。

构建产物保存在各自的 release 目录中。

七、首次配置服务器

服务器至少需要:

  • Docker Engine
  • Docker Compose 插件
  • MySQL 8
  • Nginx
  • SSH 服务

Ubuntu 服务器建议按照 Docker Engine 官方安装文档配置 Docker 官方软件源,然后安装:

Bash
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

验证:

Bash
sudo systemctl status docker
sudo docker run hello-world
docker compose version

创建项目目录:

Bash
mkdir -p /www/wwwroot/zhu-blog/storage/uploads
cd /www/wwwroot/zhu-blog

服务器目录中需要预先准备:

Text
/www/wwwroot/zhu-blog/
├─ docker-compose.bt.yml
├─ .env.backend
├─ .env.frontend
└─ storage/
   └─ uploads/

后端生产配置

创建 .env.backend

ENV
DATABASE_URL=mysql://blog:数据库密码@127.0.0.1:3306/blog

BLOG__DATABASE__MAX_CONNECTIONS=10
BLOG__DATABASE__MIN_CONNECTIONS=1
BLOG__DATABASE__ACQUIRE_TIMEOUT_SECONDS=5

BLOG__JWT__SECRET=替换成随机生成的高强度密钥
BLOG__JWT__ISSUER=blog-backend
BLOG__JWT__AUDIENCE=blog-admin
BLOG__JWT__ACCESS_TOKEN_EXPIRES_SECONDS=900
BLOG__AUTH__REFRESH_TOKEN_EXPIRES_SECONDS=7776000

BLOG__SMTP__ENABLED=false
RUST_LOG=info,sqlx=warn

可通过下面的命令生成 JWT 密钥:

Bash
openssl rand -base64 48

前端生产配置

创建 .env.frontend

ENV
NUXT_PUBLIC_API_BASE=http://127.0.0.1:5800
NUXT_PUBLIC_SITE_URL=https://你的域名

Docker Compose 配置

创建 docker-compose.bt.yml

YAML
name: qiaofu-blog

services:
  backend:
    image: qiaofu-blog-backend:local
    restart: unless-stopped
    network_mode: host
    env_file:
      - .env.backend
    environment:
      BLOG__SERVER__HOST: 127.0.0.1
      BLOG__SERVER__PORT: 5800
      BLOG__UPLOAD__DIRECTORY: /app/storage/uploads
      BLOG__GEOIP__DATABASE: /app/storage/geoip/ip2region.xdb
    volumes:
      - ./storage/uploads:/app/storage/uploads
    healthcheck:
      test:
        - CMD
        - curl
        - -fsS
        - http://127.0.0.1:5800/api/health
      interval: 10s
      timeout: 5s
      retries: 12
    mem_limit: 384m
    logging:
      driver: json-file
      options:
        max-size: 10m
        max-file: "3"

  frontend:
    image: qiaofu-blog-frontend:local
    restart: unless-stopped
    network_mode: host
    env_file:
      - .env.frontend
    environment:
      HOST: 127.0.0.1
      PORT: 3000
    depends_on:
      backend:
        condition: service_healthy
    mem_limit: 512m
    logging:
      driver: json-file
      options:
        max-size: 10m
        max-file: "3"

这里的 qiaofu-blog-* 是当前构建脚本使用的内部镜像名称。

检查配置:

Bash
docker compose -f docker-compose.bt.yml config --quiet

八、配置 SSH 密钥

自动部署不应该把服务器密码明文写进代码。

在 Windows 开发机生成密钥:

PowerShell
ssh-keygen -t ed25519 -C "qiaofu-deploy"

默认会生成:

Text
C:\Users\你的用户名\.ssh\id_ed25519
C:\Users\你的用户名\.ssh\id_ed25519.pub

把公钥添加到服务器:

PowerShell
Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" |
  ssh root@你的服务器地址 "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys"

首次执行会要求输入一次服务器密码。

之后验证免密登录:

PowerShell
ssh root@你的服务器地址

生产环境更推荐创建权限受限的部署用户,而不是长期使用 root。

九、一键部署脚本

在前后端目录的共同根目录创建 deploy.ps1

PowerShell
param(
  [string]$Version = (Get-Date -Format 'yyyyMMdd-HHmmss'),

  [ValidateSet('linux/amd64', 'linux/arm64')]
  [string]$Platform = 'linux/amd64'
)

$ErrorActionPreference = 'Stop'

# 根据自己的服务器修改
$Server = 'YOUR_SERVER_IP_OR_DOMAIN'
$User = 'root'
$RemoteDir = '/www/wwwroot/zhu-blog'
$KeyFile = Join-Path $env:USERPROFILE '.ssh\id_ed25519'

if ($Server -eq 'YOUR_SERVER_IP_OR_DOMAIN') {
  throw '请先填写服务器地址。'
}

if (-not (Test-Path -LiteralPath $KeyFile)) {
  throw "没有找到 SSH 私钥:$KeyFile"
}

$SshPath = (Get-Command ssh -ErrorAction Stop).Source
$ScpPath = (Get-Command scp -ErrorAction Stop).Source

$root = $PSScriptRoot
$backendDir = Join-Path $root 'backend\blog-backend'
$frontendDir = Join-Path $root 'frontend\blog-frontend'
$platformSlug = $Platform.Replace('/', '-')

$backendTar = "qiaofu-blog-backend-$Version-$platformSlug.tar"
$frontendTar = "qiaofu-blog-frontend-$Version-$platformSlug.tar"

Write-Host "==> [1/5] 构建后端镜像:$Platform"

& (Join-Path $backendDir 'scripts\package-image.ps1') `
  -Version $Version `
  -Platform $Platform

Write-Host "==> [2/5] 构建前端镜像:$Platform"

& (Join-Path $frontendDir 'scripts\package-image.ps1') `
  -Version $Version `
  -Platform $Platform

$backendTarPath = Join-Path $backendDir "release\$backendTar"
$frontendTarPath = Join-Path $frontendDir "release\$frontendTar"

if (-not (Test-Path -LiteralPath $backendTarPath)) {
  throw "没有找到后端构建产物:$backendTarPath"
}

if (-not (Test-Path -LiteralPath $frontendTarPath)) {
  throw "没有找到前端构建产物:$frontendTarPath"
}

Write-Host '==> [3/5] 上传后端镜像'

& $ScpPath `
  -i $KeyFile `
  $backendTarPath `
  "${User}@${Server}:${RemoteDir}/"

if ($LASTEXITCODE -ne 0) {
  throw '后端镜像上传失败。'
}

Write-Host '==> [4/5] 上传前端镜像'

& $ScpPath `
  -i $KeyFile `
  $frontendTarPath `
  "${User}@${Server}:${RemoteDir}/"

if ($LASTEXITCODE -ne 0) {
  throw '前端镜像上传失败。'
}

Write-Host '==> [5/5] 加载镜像并重建服务'

$remoteCommand = @(
  'set -e'
  "cd '$RemoteDir'"
  "docker load -i '$backendTar'"
  "docker load -i '$frontendTar'"
  "rm -f '$backendTar' '$frontendTar'"
  'docker compose -f docker-compose.bt.yml config --quiet'
  'docker compose -f docker-compose.bt.yml up -d --force-recreate --remove-orphans'
  'docker compose -f docker-compose.bt.yml ps'
) -join '; '

& $SshPath `
  -i $KeyFile `
  "${User}@${Server}" `
  $remoteCommand

if ($LASTEXITCODE -ne 0) {
  throw '服务器部署失败。'
}

Write-Host "==> 部署完成:$Version"

该脚本依次执行五个步骤:

  1. 构建后端 Linux 镜像。
  2. 构建前端 Linux 镜像。
  3. 上传后端镜像。
  4. 上传前端镜像。
  5. 在服务器加载镜像并重建容器。

十、执行一键部署

普通 AMD64 云服务器:

PowerShell
.\deploy.ps1 `
  -Version v1.0.0 `
  -Platform linux/amd64

ARM64 服务器:

PowerShell
.\deploy.ps1 `
  -Version v1.0.0 `
  -Platform linux/arm64

部署成功后检查:

Bash
cd /www/wwwroot/zhu-blog

docker compose -f docker-compose.bt.yml ps
docker compose -f docker-compose.bt.yml logs --tail=100 backend
docker compose -f docker-compose.bt.yml logs --tail=100 frontend

curl -fsS http://127.0.0.1:5800/api/health
curl -I http://127.0.0.1:3000

首次上线还需要创建管理员:

Bash
docker compose -f docker-compose.bt.yml run --rm backend create_admin

后端启动时会自动执行数据库迁移,不需要手动执行 SQL 迁移文件。

十一、配置 Nginx 和 HTTPS

Nginx 只需要反向代理前端的 3000 端口:

NGINX
location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

然后为域名申请 SSL 证书,并配置 HTTP 自动跳转 HTTPS。

前后端分别只监听:

Text
127.0.0.1:3000
127.0.0.1:5800

因此服务器防火墙不需要向公网开放 30005800,公网只开放 80443 和受控的 SSH 端口。

十二、更新与回滚

后续更新只需要拉取最新代码:

PowerShell
git -C frontend/blog-frontend pull
git -C backend/blog-backend pull

使用新的版本号部署:

PowerShell
.\deploy.ps1 -Version v1.0.1

每次部署都应使用不同版本号,便于追踪发布记录。

部署前需要单独备份:

  • MySQL 数据库
  • .env.backend
  • .env.frontend
  • storage/uploads

发布脚本不会覆盖服务器环境变量和上传目录,但数据库迁移可能修改表结构,因此重要更新前仍然需要备份。

结语

到这里,QiaoFu Blog 已经形成了一套完整的发布链路:

Text
拉取代码
→ 配置开发环境
→ 本地运行验证
→ Docker 构建
→ 导出 Linux 镜像
→ SSH 上传
→ Compose 重建服务
→ 健康检查

服务器不需要源码,也不需要安装 Rust 和 Node.js。日常更新时,只需要在开发机完成修改和检查,然后执行一条 PowerShell 命令,就能同时完成前后端构建、上传和服务更新。

这套流程目前仍然比较朴素,但已经足以支撑个人项目稳定、可重复地迭代发布。


TAGS #Rust #Vue 3 #工程化
DISCUSSION

留下你的想法

0 条评论
还没有公开评论,来成为第一个留言的人吧。

写下评论

评论审核通过后公开显示,邮箱只用于识别身份,不会对外展示。

保持热爱,奔赴山海。

STAY CURIOUS · KEEP GOING