适用场景:Nuxt 3 / Nuxt 4 项目,构建后需要通过 Node.js 运行 .output/server/index.mjs
部署方式:GitHub + Self-hosted Runner + Docker Compose + 宝塔/Nginx。
示例项目:jiali-blog-web
示例端口:3004


1. 最终效果

完成后,整个部署流程会变成:

Text
本地修改 Nuxt 项目
        ↓
git push main
        ↓
GitHub Actions 自动触发
        ↓
服务器上的 Self-hosted Runner 接收任务
        ↓
拉取最新代码
        ↓
Docker 构建 Nuxt
        ↓
npm ci
        ↓
npm run build
        ↓
生成 .output
        ↓
node .output/server/index.mjs
        ↓
启动新容器
        ↓
检查首页 /
        ↓
部署完成

以后发布新版本,基本只需要:

Shell
git push origin main

服务器会自动更新,不需要再登录宝塔手动构建。

2. 准备工作

开始之前,服务器需要已经安装:

Text
Docker
Docker Compose
Git
SSH

同时建议准备一个专门负责部署的 Linux 用户:

Text
deploy

不要让 GitHub Actions 长期使用 root

本教程约定服务器目录结构:

Text
/www/git/
└── jiali-blog-web/

/www/env/
└── jiali-blog-web.env

/home/deploy/
├── runner-cache/
│   └── actions-runner-linux-x64-2.336.0.tar.gz
└── runners/
    └── jiali-blog-web/

其中:

Text
/www/git/

存项目源码。

Text
/www/env/

存生产环境变量。

Text
/home/deploy/runners/

存 GitHub Self-hosted Runner。

3. 创建 deploy 用户

如果服务器已经有 deploy 用户,可以跳过这一节。

root 执行:

Shell
useradd -m -s /bin/bash deploy

把 deploy 加入 docker 用户组:

Shell
usermod -aG docker deploy

确认:

Shell
id deploy

然后切换:

Shell
su - deploy

测试 Docker:

Shell
docker ps

如果可以正常执行,说明 Docker 权限没有问题。

4. 配置 deploy 用户访问 GitHub

服务器需要自己从 GitHub 拉代码,所以 deploy 用户需要 GitHub SSH 权限。

切换到 deploy:

Shell
su - deploy

生成 SSH Key:

Shell
ssh-keygen \
  -t ed25519 \
  -C "deploy@server" \
  -f ~/.ssh/id_ed25519 \
  -N ""

查看公钥:

Shell
cat ~/.ssh/id_ed25519.pub

复制输出内容。

然后进入 GitHub:

Text
Settings
→ SSH and GPG keys
→ New SSH key

类型选择:

Text
Authentication Key

保存以后,在服务器测试:

Shell
ssh -T git@github.com

如果看到类似:

Text
Hi xxx! You've successfully authenticated...

说明配置完成。

不要把 ~/.ssh/id_ed25519 私钥发给别人,也不要提交到 Git 仓库。

5. 给 /www/git 配置权限

推荐让 root 管理 /www/git 本身,同时允许 deploy 在里面创建项目目录。

root 执行:

Shell
chown root:deploy /www/git
chmod 775 /www/git

确认:

Shell
ls -ld /www/git

正常类似:

Text
drwxrwxr-x root deploy ... /www/git

这样 deploy 用户就可以直接 clone 项目。

6. 克隆 Nuxt 项目

切换到 deploy:

Shell
su - deploy

进入源码目录:

Shell
cd /www/git

克隆项目:

Shell
git clone \
  --depth=1 \
  --branch main \
  --single-branch \
  git@github.com:LeviQin/jiali-blog-web.git

进入项目:

Shell
cd /www/git/jiali-blog-web

检查:

Shell
git remote -v

再测试:

Shell
git fetch --depth=1 origin main

如果没有报错,说明服务器可以正常拉取 GitHub 代码。


7. 一个很重要的 Git 权限规则

以后 /www/git/jiali-blog-web 统一让 deploy 用户操作 Git。

不要在 root 下直接:

Shell
git pull

否则可能出现:

Text
fatal: detected dubious ownership in repository

正确做法:

Shell
su - deploy
cd /www/git/jiali-blog-web
git fetch --depth=1 origin main
git reset --hard origin/main

也可以在 root 下直接指定 deploy:

Shell
sudo -u deploy \
  git -C /www/git/jiali-blog-web \
  fetch --depth=1 origin main

推荐还是统一切到 deploy,比较不容易混乱。

8. 确认 Nuxt 的 Node 版本

先看 package.json

例如:

JSON
{
  "engines": {
    "node": ">=22.19.0"
  }
}

那么 Dockerfile 就不要使用:

Dockerfile
FROM node:20

否则可能出现依赖或构建兼容问题。

本教程示例项目要求:

Text
Node >= 22.19.0

所以使用:

Text
node:22.19-alpine

9. Nuxt 生产环境应该怎么运行

Nuxt 开发时通常是:

Shell
npm run dev

但生产环境不是运行 npm run dev,也不建议使用 npm run preview

正式部署应该:

Shell
npm run build

Nuxt 构建以后会生成:

Text
.output/

最终 Node 服务入口:

Text
.output/server/index.mjs

生产启动命令:

Shell
node .output/server/index.mjs

10. Dockerfile

在项目根目录创建:

Text
Dockerfile

完整示例:

Dockerfile
# ==============================
# Build Stage
# ==============================
FROM node:22.19-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

ENV NODE_ENV=production

RUN npm run build


# ==============================
# Production Stage
# ==============================
FROM node:22.19-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3004

COPY --from=builder /app/.output ./.output

EXPOSE 3004

CMD ["node", ".output/server/index.mjs"]

这里使用的是 Docker 多阶段构建。

第一阶段 builder 负责:

Text
安装依赖
↓
构建 Nuxt
↓
生成 .output

第二阶段 runner 只复制:

Text
.output

所以正式运行的镜像不会包含完整源码和开发依赖。

11. .dockerignore

项目根目录创建:

Text
.dockerignore

推荐:

DOCKERIGNORE
node_modules
.nuxt
.output

.git
.github

.env
.env.*
!.env.example

.idea
.vscode

npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

README.md

这样可以避免无用文件进入 Docker build context。

特别注意:.env.env.production 不要直接打进 Docker 镜像。

12. 创建生产环境变量文件

推荐把生产环境变量放在服务器:

Text
/www/env/jiali-blog-web.env

而不是提交到 GitHub。

root 执行:

Shell
mkdir -p /www/env
touch /www/env/jiali-blog-web.env

修改权限:

Shell
chown root:deploy /www/env/jiali-blog-web.env
chmod 640 /www/env/jiali-blog-web.env

编辑:

Shell
vim /www/env/jiali-blog-web.env

或者用宝塔文件管理器编辑。

例如:

ENV
NUXT_PUBLIC_API_BASE=https://api.example.com

不要把真实密码、Token、数据库密码、Secret 提交到 GitHub。

13. Nuxt 环境变量需要注意什么

Nuxt 环境变量大致分两类:

Text
运行时变量
构建时变量

推荐优先使用 Nuxt runtimeConfig

例如:

TypeScript
export default defineNuxtConfig({
  runtimeConfig: {
    apiSecret: '',
    public: {
      apiBase: ''
    }
  }
})

生产环境可以使用:

ENV
NUXT_API_SECRET=xxx
NUXT_PUBLIC_API_BASE=https://api.example.com

这些变量可以通过 Docker Compose 的 env_file 在容器运行时注入。

但如果某个变量在 npm run build 阶段就被 Vite 直接编译进前端代码,那么仅仅设置 env_file 可能不够。

这种情况需要通过 Docker build.args 或构建阶段环境变量处理。

如果不确定,优先把配置放进 Nuxt runtimeConfig

14. docker-compose.yml

项目根目录创建:

Text
docker-compose.yml

完整示例:

YAML
services:
  jiali-blog-web:
    build:
      context: .
      dockerfile: Dockerfile

    image: jiali-blog-web:latest

    container_name: jiali-blog-web

    restart: unless-stopped

    ports:
      - "127.0.0.1:3004:3004"

    env_file:
      - /www/env/jiali-blog-web.env

    environment:
      NODE_ENV: production
      HOST: 0.0.0.0
      PORT: 3004

    healthcheck:
      test:
        [
          "CMD",
          "node",
          "-e",
          "fetch('http://127.0.0.1:3004/').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
        ]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

    stop_grace_period: 10s

这里:

Text
127.0.0.1:3004:3004

表示:

Text
服务器 127.0.0.1:3004
        ↓
Docker 容器 3004
        ↓
Nuxt Nitro 3004

只绑定 127.0.0.1,意味着 3004 不直接暴露到公网。

公网访问统一通过宝塔 Nginx 反向代理。

15. 前端项目一定要 /health 吗

不需要。

Nuxt 前端项目可以直接检查首页 /

Docker Healthcheck:

YAML
healthcheck:
  test:
    [
      "CMD",
      "node",
      "-e",
      "fetch('http://127.0.0.1:3004/').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
    ]

只要首页返回 2xx,Docker 就会认为容器 healthy

如果首页 SSR 强依赖第三方服务,第三方服务故障可能导致首页返回 500,这时 Docker 也会变成 unhealthy。

简单博客、个人站点一般直接检查 / 就够用了。

16. 第一次手动 Docker 部署

正式接入自动部署之前,建议先手动跑一次。

切换 deploy:

Shell
su - deploy

进入项目:

Shell
cd /www/git/jiali-blog-web

先检查端口有没有被占用:

Shell
ss -lntp | grep ':3004'

没有输出,说明 3004 当前没有进程监听。

检查 Compose:

Shell
docker compose config

docker compose config 可能会展开环境变量。不要把包含真实 Secret 的完整输出复制到公开日志或 GitHub Actions。

开始第一次构建:

Shell
docker compose up \
  -d \
  --build \
  --wait \
  --wait-timeout 90

第一次构建通常会比较慢,因为需要:

Text
下载 Node 镜像
↓
npm ci
↓
Nuxt build
↓
创建生产镜像
↓
启动容器

17. 检查容器状态

执行:

Shell
docker compose ps

正常应该类似:

Text
NAME             IMAGE                   STATUS
jiali-blog-web   jiali-blog-web:latest   Up ... (healthy)

也可以:

Shell
docker inspect jiali-blog-web \
  --format='Status={{.State.Status}} Health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'

正常:

Text
Status=running Health=healthy

18. 测试 Nuxt 首页

服务器执行:

Shell
curl -I http://127.0.0.1:3004/

正常应该看到:

Text
HTTP/1.1 200 OK

如果不是 200,查看日志:

Shell
docker compose logs --tail=200

或者:

Shell
docker logs --tail=200 jiali-blog-web

19. 创建 GitHub Self-hosted Runner

这里使用 Self-hosted Runner,让服务器主动连接 GitHub,不需要 GitHub Runner SSH 登录服务器。

如果服务器已经下载过 Runner,可以缓存到:

Text
/home/deploy/runner-cache/

例如:

Text
/home/deploy/runner-cache/actions-runner-linux-x64-2.336.0.tar.gz

这样每增加一个项目,不用重新下载。

20. 创建项目 Runner 目录

root:

Shell
mkdir -p /home/deploy/runners/jiali-blog-web

设置权限:

Shell
chown -R deploy:deploy /home/deploy/runners/jiali-blog-web

切换 deploy:

Shell
su - deploy

进入:

Shell
cd /home/deploy/runners/jiali-blog-web

确认:

Shell
pwd

必须是:

Text
/home/deploy/runners/jiali-blog-web

然后解压:

Shell
tar xzf \
  /home/deploy/runner-cache/actions-runner-linux-x64-2.336.0.tar.gz

成功后:

Shell
ls -la

应该看到:

Text
bin/
externals/
config.sh
env.sh
run.sh
svc.sh

21. Runner 解压出现 Permission denied

如果看到:

Text
tar: ./bin: Cannot mkdir: Permission denied
tar: ./config.sh: Cannot open: Permission denied

说明 Runner 目录权限不属于 deploy。

退出:

Shell
exit

root 重新设置:

Shell
chown -R deploy:deploy \
  /home/deploy/runners/jiali-blog-web

如果这个 Runner 还没有注册,也可以直接重建:

Shell
rm -rf /home/deploy/runners/jiali-blog-web
mkdir -p /home/deploy/runners/jiali-blog-web
chown -R deploy:deploy \
  /home/deploy/runners/jiali-blog-web

然后再切换 deploy 解压。

22. 在 GitHub 注册 Runner

进入:

Text
GitHub 仓库
→ Settings
→ Actions
→ Runners
→ New self-hosted runner

选择:

Text
Linux
x64

如果已经有 Runner 缓存,Download 部分不用再执行。

只执行 GitHub 提供的注册命令,例如:

Shell
./config.sh \
  --url https://github.com/LeviQin/jiali-blog-web \
  --token GitHub临时Token

Runner 注册 Token 是临时 Token,不要截图发到公开地方,也不要写进配置文件。


23. Runner 注册时怎么填写

Runner group:直接按 Enter,使用 Default。

Runner name:

Text
jiali-blog-web-runner

Additional labels:

Text
jiali-blog-web,docker

Work folder:直接 Enter,使用默认 _work

最终:

Text
Runner group:
Default

Runner name:
jiali-blog-web-runner

Labels:
jiali-blog-web,docker

Work folder:
_work

24. Runner 安装成 systemd 服务

注册以后不要把 ./run.sh 作为正式运行方式。

退出到 root:

Shell
exit

进入 Runner:

Shell
cd /home/deploy/runners/jiali-blog-web

安装:

Shell
./svc.sh install deploy

启动:

Shell
./svc.sh start

查看:

Shell
./svc.sh status

正常:

Text
Active: active (running)

GitHub:

Text
Settings
→ Actions
→ Runners

应该看到:

Text
jiali-blog-web-runner
Idle

说明 Runner 正在等待部署任务。

Runner 已经安装 systemd 后,不要再执行:

Shell
./run.sh

否则可能出现:

Text
A session for this runner already exists

日常管理使用:

Shell
./svc.sh status
./svc.sh stop
./svc.sh start

25. 创建 GitHub Actions deploy.yml

项目创建:

Text
.github/workflows/deploy.yml

完整示例:

YAML
name: Deploy Jiali Blog Web

on:
  push:
    branches:
      - main

concurrency:
  group: jiali-blog-web-production
  cancel-in-progress: false

jobs:
  deploy:
    name: Deploy Production

    runs-on:
      - self-hosted
      - Linux
      - X64
      - jiali-blog-web

    timeout-minutes: 30

    steps:
      - name: Update source code
        run: |
          set -e

          cd /www/git/jiali-blog-web

          echo "========== 更新代码 =========="

          git fetch --depth=1 origin main
          git reset --hard origin/main

          echo ""
          echo "========== 当前部署版本 =========="
          git log -1 --oneline

      - name: Deploy Docker
        run: |
          set -e

          cd /www/git/jiali-blog-web

          echo "========== 构建并更新 Nuxt 容器 =========="

          if ! docker compose up \
            -d \
            --build \
            --remove-orphans \
            --wait \
            --wait-timeout 90; then

            echo ""
            echo "========== 部署失败 =========="

            echo ""
            echo "========== Docker Compose 状态 =========="
            docker compose ps || true

            echo ""
            echo "========== 容器日志 =========="
            docker compose logs --tail=200 || true

            echo ""
            echo "========== 容器状态 =========="
            docker inspect jiali-blog-web \
              --format='Status={{.State.Status}} ExitCode={{.State.ExitCode}}' || true

            echo ""
            echo "========== 健康检查详情 =========="
            docker inspect jiali-blog-web \
              --format='{{json .State.Health}}' || true

            exit 1
          fi

      - name: Check container
        run: |
          set -e

          cd /www/git/jiali-blog-web

          echo "========== Docker Compose 状态 =========="
          docker compose ps

          echo ""
          echo "========== jiali-blog-web 状态 =========="

          docker inspect jiali-blog-web \
            --format='Status={{.State.Status}} Health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'

      - name: Verify website
        run: |
          set -e

          echo "========== Nuxt 首页检查 =========="

          curl \
            --fail \
            --silent \
            --show-error \
            --output /dev/null \
            http://127.0.0.1:3004/

          echo "Nuxt 首页访问正常"

      - name: Cleanup Docker images
        run: |
          set -e

          echo "========== 清理无用 Docker 镜像 =========="
          docker image prune -f

      - name: Deployment completed
        run: |
          set -e

          cd /www/git/jiali-blog-web

          echo ""
          echo "========================================"
          echo "jiali-blog-web 部署成功"
          echo "========================================"

          echo ""
          echo "========== 部署版本 =========="
          git log -1 --oneline

          echo ""
          echo "========== 最终容器状态 =========="
          docker compose ps

          echo ""
          echo "========== 网站地址 =========="
          echo "http://127.0.0.1:3004/"

26. 为什么 deploy.yml 不使用 actions/checkout

Self-hosted Runner 本身就在服务器,服务器已经有:

Text
/www/git/jiali-blog-web

所以没必要再 checkout 一份到 Runner 的 _work 目录。

我们直接:

Shell
cd /www/git/jiali-blog-web
git fetch --depth=1 origin main
git reset --hard origin/main

这样部署目录固定,也可以继续使用服务器现有的 GitHub SSH Key。

27. 提交部署文件

本地项目确认包含:

Text
Dockerfile
.dockerignore
docker-compose.yml
.github/workflows/deploy.yml

提交:

Shell
git add .
git commit -m "ci: add nuxt docker deployment"
git push origin main

GitHub Actions 会自动触发。

28. 第一次 Actions 怎么检查

进入:

Text
GitHub
→ jiali-blog-web
→ Actions
→ Deploy Jiali Blog Web

正常步骤:

Text
Update source code       ✅
Deploy Docker            ✅
Check container          ✅
Verify website           ✅
Cleanup Docker images    ✅
Deployment completed     ✅

如果 Runner 还没启动,可能显示:

Text
Waiting for a runner to pick up this job

启动 Runner systemd 后,它会自动接任务。

29. 宝塔 Nginx 反向代理

Docker 只监听:

Text
127.0.0.1:3004

所以公网需要通过宝塔 Nginx。

假设网站:

Text
https://example.com

反向代理目标:

Text
http://127.0.0.1:3004

Nginx 核心配置类似:

Nginx
location / {
    proxy_pass http://127.0.0.1:3004;

    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_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

SSL 继续由宝塔/Nginx 管理。

Docker 内部不需要自己配置 HTTPS。

完整链路:

Text
浏览器
  ↓
HTTPS :443
  ↓
Nginx
  ↓
127.0.0.1:3004
  ↓
Docker
  ↓
Nuxt

30. 发布新版本

全部配置完成以后,日常流程非常简单。

本地:

Shell
git add .
git commit -m "feat: update website"
git push origin main

后面的事情自动完成:

Text
GitHub
↓
Runner
↓
拉代码
↓
重新构建镜像
↓
更新容器
↓
检查网站
↓
上线

31. 常用服务器命令

进入项目:

Shell
su - deploy
cd /www/git/jiali-blog-web

查看容器:

Shell
docker compose ps

查看日志:

Shell
docker compose logs --tail=200

持续看日志:

Shell
docker compose logs -f

重新构建:

Shell
docker compose up -d --build

带健康检查等待:

Shell
docker compose up \
  -d \
  --build \
  --wait \
  --wait-timeout 90

停止:

Shell
docker compose stop

停止并删除容器:

Shell
docker compose down

检查首页:

Shell
curl -I http://127.0.0.1:3004/

检查 Docker Health:

Shell
docker inspect jiali-blog-web \
  --format='Status={{.State.Status}} Health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'

清理无用镜像:

Shell
docker image prune -f

32. 常见问题:端口被占用

错误:

Text
Bind for 127.0.0.1:3004 failed:
port is already allocated

检查:

Shell
ss -lntp | grep ':3004'

或者:

Shell
docker ps --filter publish=3004

如果是旧容器:

Shell
docker stop 旧容器名
docker rm 旧容器名

再部署:

Shell
docker compose up -d --build

33. 常见问题:容器 unhealthy

查看:

Shell
docker compose ps

如果是:

Text
unhealthy

先不要重新 build。

查看日志:

Shell
docker compose logs --tail=200

再看 Healthcheck:

Shell
docker inspect jiali-blog-web \
  --format='{{json .State.Health}}'

手动检查:

Shell
curl -I http://127.0.0.1:3004/

如果 Nuxt 本身可以正常运行但首页不是 2xx,需要检查:

Text
SSR 报错
API 请求失败
环境变量错误
路由重定向
运行端口错误

34. 常见问题:容器一直 Restarting

如果:

Text
Restarting (1)

这通常不是 Docker Healthcheck 的问题,而是 Node 进程启动后直接崩了。

查看:

Shell
docker logs --tail=200 jiali-blog-web

重点看最前面的:

Text
Error
ReferenceError
TypeError
Cannot find module

先修 Node/Nuxt 报错,再重新构建。

35. 常见问题:npm ci 失败

如果 Dockerfile:

Dockerfile
RUN npm ci

报错,通常是 package.jsonpackage-lock.json 不同步。

本地执行:

Shell
npm install

确认 package-lock.json 有更新,然后提交:

Shell
git add package-lock.json
git commit -m "chore: update package lock"
git push

36. 常见问题:Node 版本不够

如果 package.json:

JSON
"engines": {
  "node": ">=22.19.0"
}

Dockerfile 却是:

Dockerfile
FROM node:20

可能出现:

Text
Unsupported engine
Build failed
依赖安装失败

直接把 Docker Node 版本和项目要求统一,例如:

Dockerfile
FROM node:22.19-alpine

37. 常见问题:Git dubious ownership

错误:

Text
fatal: detected dubious ownership in repository

不要随手执行:

Shell
git config --global --add safe.directory ...

如果项目本来就应该由 deploy 管理,更合理的处理方式:

Shell
chown -R deploy:deploy /www/git/jiali-blog-web

之后:

Shell
su - deploy
cd /www/git/jiali-blog-web
git pull

38. 常见问题:Runner Permission denied

Runner 解压出现:

Text
Permission denied

检查:

Shell
ls -ld /home/deploy/runners/jiali-blog-web

应该是:

Text
deploy deploy

不是:

Text
root root

修复:

Shell
chown -R deploy:deploy \
  /home/deploy/runners/jiali-blog-web

39. 常见问题:Runner duplicate session

看到:

Text
A session for this runner already exists

通常是 systemd 已经启动,你又执行了一次:

Shell
./run.sh

检查:

Shell
pgrep -af "Runner.Listener"

Runner 已经 systemd 托管后不要再手动 ./run.sh

40. 常见问题:Runner 显示 Offline

root:

Shell
cd /home/deploy/runners/jiali-blog-web
./svc.sh status

如果没启动:

Shell
./svc.sh start

也可以在 GitHub:

Text
Settings
→ Actions
→ Runners

确认 Runner 是否在线。

41. 更新环境变量以后怎么办

如果只是 Nuxt 运行时环境变量,修改:

Text
/www/env/jiali-blog-web.env

然后重建容器配置:

Shell
cd /www/git/jiali-blog-web
docker compose up -d --force-recreate

如果变量属于构建期变量,则需要重新:

Shell
docker compose up -d --build

42. 多个 Nuxt 项目怎么部署

可以继续使用同一台服务器。

例如:

Text
/www/git/
├── jiali-blog-web
├── project-a-web
└── project-b-web

/www/env/
├── jiali-blog-web.env
├── project-a-web.env
└── project-b-web.env

/home/deploy/runners/
├── jiali-blog-web
├── project-a-web
└── project-b-web

端口不要重复:

Text
jiali-blog-web    → 3004
project-a-web     → 3005
project-b-web     → 3006

每个项目都有自己的 Runner、Runner Label、Compose、Container、Image、端口和 env 文件。

43. 新 Nuxt 项目部署检查表

以后新增项目,可以按这个顺序做:

Text
1. 确认 package.json 的 Node 版本
2. 添加 Dockerfile
3. 添加 .dockerignore
4. 添加 docker-compose.yml
5. 准备 /www/env/<project>.env
6. deploy 用户 clone 项目
7. 创建 /home/deploy/runners/<project>
8. 解压 Runner 缓存
9. GitHub 注册 Runner
10. 安装 systemd
11. 手动 docker compose up -d --build
12. 检查容器 healthy
13. curl 检查首页
14. 添加 deploy.yml
15. push main
16. 检查 GitHub Actions
17. 宝塔配置 Nginx 反向代理
18. 完成

44. 推荐的最终目录结构

Text
/home/deploy/
├── runner-cache/
│   └── actions-runner-linux-x64-2.336.0.tar.gz
└── runners/
    ├── jiali-blog-web/
    ├── project-a-web/
    └── project-b-web/

/www/git/
├── jiali-blog-web/
├── project-a-web/
└── project-b-web/

/www/env/
├── jiali-blog-web.env
├── project-a-web.env
└── project-b-web.env

项目仓库:

Text
jiali-blog-web/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── package.json
├── package-lock.json
├── nuxt.config.ts
└── ...

45. 一套可以长期复用的规则

建议以后统一保持:

Text
Git 操作:deploy 用户
Docker 操作:deploy 用户
Runner 服务:systemd
Runner 运行用户:deploy
源码:/www/git/<project>
环境变量:/www/env/<project>.env
Runner:/home/deploy/runners/<project>
镜像名:<project>:latest
容器名:<project>
Runner label:<project>

这样部署多个项目时不会越来越乱。

46. 最终总结

Nuxt 和普通 Node/Koa 后端最大的区别,是 Nuxt 部署前需要先构建:

Text
源码
↓
npm ci
↓
npm run build
↓
.output
↓
node .output/server/index.mjs

所以 Docker 推荐使用多阶段构建,生产容器只保留 .output

自动部署则继续使用:

Text
GitHub Actions
+
Self-hosted Runner
+
Docker Compose

完成一次以后,后续发布基本就只剩:

Shell
git push origin main

其余步骤全部由服务器自动完成。

阅读进度 0%