GitHub Actions 自动部署

View source
使用 GitHub Actions + GHCR + Watchtower 实现 Docker 容器自动部署

架构概览

GitHub 仓库
  └── push to main
        │
        ▼
GitHub Actions(构建 Docker 镜像)
  └── 推送至 GHCR(GitHub Container Registry)
        │
        ▼
Watchtower(每 60 秒轮询 GHCR)
  └── 检测到新镜像 → 自动拉取并重启容器
        │
        ▼
Docker 容器(加入 webnet 网络)
  └── nginx 通过容器名反向代理
        │
        ▼
用户访问(HTTPS)

项目端配置

Dockerfile

多阶段构建,最终镜像只包含 .output/,不含源码:

Dockerfile
FROM node:24-alpine AS base
RUN corepack enable

FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY docs/package.json ./docs/
RUN corepack install && pnpm install --frozen-lockfile

FROM deps AS build
COPY . .
RUN pnpm exec nuxt-module-build build --stub \
 && pnpm exec nuxt-module-build prepare \
 && pnpm exec nuxt build docs

FROM node:24-alpine AS runtime
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/docs/.output ./
USER app
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server/index.mjs"]
corepack install 读取 package.jsonpackageManager 字段自动激活对应 pnpm 版本,无需手动锁定版本号。
GitHub CI 镜像加速指南

GitHub Actions 工作流

.github/workflows/deploy.yml
name: Deploy Docs

on:
  push:
    branches:
      - main

env:
  REGISTRY: ghcr.io
  IMAGE: ghcr.io/${{ github.repository }}/docs

permissions:
  contents: read
  packages: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 20

    steps:
      - uses: actions/checkout@v6
      - uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v7
        with:
          context: .
          push: true
          tags: |
            ${{ env.IMAGE }}:latest
            ${{ env.IMAGE }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
无需配置任何 GitHub SecretsGITHUB_TOKEN 由 GitHub 自动提供。
GHCR 镜像命名规则GITHUB_TOKEN 只能写入当前仓库关联的包。镜像名必须是 ghcr.io/<owner>/<repo> 或其子路径(如 ghcr.io/<owner>/<repo>/docs)。如果使用 ghcr.io/mhaibaraai/movk-nuxt-docs(在仓库名后追加 -docs),会被 GHCR 视为另一个独立包GITHUB_TOKEN 无权写入,推送时报 permission_denied: write_package。正确做法是使用子路径格式 ghcr.io/${{ github.repository }}/docs,对应镜像名为 ghcr.io/mhaibaraai/movk-nuxt/docs

服务器端配置

Watchtower(自动更新)

~/watchtower/docker-compose.yml
services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    restart: unless-stopped
    environment:
      - WATCHTOWER_POLL_INTERVAL=60
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_INCLUDE_RESTARTING=true
      - DOCKER_CONFIG=/config
      - DOCKER_API_VERSION=1.41
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /root/.docker:/config:ro
    command: movk-nuxt-docs
  • WATCHTOWER_POLL_INTERVAL=60:每 60 秒检查一次新镜像
  • WATCHTOWER_CLEANUP=true:更新后自动删除旧镜像
  • command: movk-nuxt-docs:只监控指定容器,多个容器用空格分隔
  • /root/.docker:/config:ro:挂载 GHCR 登录凭证
首次使用前需在服务器上登录 GHCR(如果镜像设为私有):
docker login ghcr.io -u <github_username> -p <github_pat>
GitHub PAT 只需 read:packages 权限。

nginx 反向代理

~/nginx/conf.d/nuxt.mhaibaraai.cn.conf
server {
    listen 80;
    server_name nuxt.mhaibaraai.cn;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name nuxt.mhaibaraai.cn;

    ssl_certificate /etc/nginx/ssl/mhaibaraai.cn.pem;
    ssl_certificate_key /etc/nginx/ssl/mhaibaraai.cn.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    location / {
        resolver 127.0.0.11 valid=30s;
        set $upstream movk-nuxt-docs:3000;
        proxy_pass http://$upstream;
        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;
    }

    access_log /var/log/nginx/nuxt.mhaibaraai.cn.access.log;
    error_log /var/log/nginx/nuxt.mhaibaraai.cn.error.log;
}
resolver 127.0.0.11 使用 Docker 内置 DNS,配合 set $upstream 变量实现动态解析。容器不存在时 nginx 仍可正常启动(返回 502),避免了 upstream 块在启动时强制解析导致的报错。

首次部署步骤

登录 GHCR(如果镜像私有)

sudo docker login ghcr.io -u <github_username> -p <github_pat>

启动 Watchtower

cd ~/watchtower && sudo docker compose up -d

手动首次启动应用容器

在服务器上创建应用目录,用 docker-compose.yml + .env 管理容器和环境变量:

mkdir ~/webs/movk-nuxt-docs
~/webs/movk-nuxt-docs/.env
NUXT_GITHUB_TOKEN=your_token_here
AI_GATEWAY_API_KEY=your_key_here
NUXT_SESSION_PASSWORD=your_password_here
~/webs/movk-nuxt-docs/docker-compose.yml
services:
  movk-nuxt-docs:
    image: ghcr.io/mhaibaraai/movk-nuxt/docs:latest
    container_name: movk-nuxt-docs
    restart: unless-stopped
    env_file: .env
    networks:
      - webnet

networks:
  webnet:
    external: true

Watchtower 只负责更新已有容器,不负责首次创建:

cd ~/webs/movk-nuxt-docs && sudo docker compose up -d

上传 nginx 配置并重载

# 上传 conf 文件到 ~/nginx/conf.d/
sudo docker exec nginx nginx -t && sudo docker exec nginx nginx -s reload

触发首次 CI 构建

git commit --allow-empty -m "chore: 触发首次部署"
git push

之后每次推送 main,全流程自动完成。

新增项目部署模板

每新增一个需要部署的 Nuxt/Node 项目,重复以下步骤:

项目仓库:新建 Dockerfile + .github/workflows/deploy.yml,修改镜像名和容器名。

Watchtower:在 ~/watchtower/docker-compose.ymlcommand 里追加容器名:

~/watchtower/docker-compose.yml
command: movk-nuxt-docs new-project-name
sh
sudo docker compose up -d

nginx:新建 conf.d/new-domain.conf,参照 nuxt.mhaibaraai.cn.conf,修改 server_nameset $upstream

sh
sudo docker exec nginx nginx -t && sudo docker exec nginx nginx -s reload

首次启动容器:创建项目目录,参照 movk-nuxt-docs 的模板创建 .envdocker-compose.yml,执行 sudo docker compose up -d

DNS:域名解析控制台添加 new-domain.mhaibaraai.cn → 服务器 IP 的 A 记录。

常用运维命令

# 查看所有容器状态
sudo docker ps

# 查看应用日志
sudo docker logs movk-nuxt-docs -f --tail 100

# 查看 Watchtower 日志(确认自动更新是否生效)
sudo docker logs watchtower -f --tail 50

# 重载 nginx 配置(无停机)
sudo docker exec nginx nginx -t && sudo docker exec nginx nginx -s reload

# 手动触发镜像更新(无需等待 Watchtower 轮询)
cd ~/webs/movk-nuxt-docs && sudo docker compose pull && sudo docker compose up -d
Copyright © 2024 - 2026 YiXuan - MIT License