Go+Nextjs SSR standalone模式 github actions编译 + docker运行 示例 next.config.ts Dockerfile start.sh

主要区别是使用前端编译后的standalone目录, 在里面使用node server.js来运行, 会比npm run start更轻量

这基本上是一个缩小尺寸的 NextJS 服务器,它只包含它的动态部分。观察表明 node_modules 仅占项目 node_modules 的 5% 左右,这在尺寸上是显着减小的(在我们有限的测试中)

后端

正常打包

前端

next.config.ts

import type { NextConfig } from "next";

  

const nextConfig: NextConfig = {

  /* config options here */

  rewrites: async () => [

    {
      source: "/api/:path*",
      destination: "http://localhost:8080/api/:path*",
    },
  ],

  // 使用 standalone 模式替代静态导出

  output: "standalone",

  // output: "export",

  // distDir: "out",

  typescript: {

    ignoreBuildErrors: true

  },

  eslint: {

    ignoreDuringBuilds: true

  }

};

  

export default nextConfig;

Dockerfile

# 最终运行镜像
FROM node:22-alpine AS runner

WORKDIR /app

# 安装必要的依赖
RUN apk add --no-cache libc6-compat tzdata

# 设置时区为中国标准时间
ENV TZ=Asia/Shanghai
  
# 复制GitHub Actions构建的Go后端
# 使用ARG指定架构,由buildx自动设置
ARG TARGETARCH
COPY vps-monitor-backend-${TARGETARCH} /app/backend/vps-monitor-backend
RUN chmod +x /app/backend/vps-monitor-backend

# 复制Next.js standalone构建结果
COPY web/.next/standalone /app/frontend/
COPY web/.next/static /app/frontend/.next/static
COPY web/public /app/frontend/public

# 添加启动脚本
COPY scripts/start.sh /app/
RUN chmod +x /app/start.sh

# 创建数据目录并设置权限
RUN mkdir -p /app/data && chmod 777 /app/data
  
# 设置工作目录环境变量
# 删除PORT环境变量,让docker-compose.yml中的设置生效
ENV NODE_ENV=production
ENV GIN_MODE=release
ENV DATA_DIR=/app/data

# 以root用户运行
USER root

# 暴露端口
EXPOSE 3000 8080

# 启动应用
CMD ["/app/start.sh"]

启动脚本 start.sh

#!/bin/sh

# 确保时区设置正确
export TZ=Asia/Shanghai

# 创建数据目录
mkdir -p /app/data

# 设置数据目录权限为全局可写
chmod -R 777 /app/data
echo "设置数据目录权限:"
ls -la /app/data

# 确保环境变量存在
PORT=${PORT:-8080}
NEXT_PORT=${NEXT_PORT:-3000}
echo "后端将使用端口: $PORT"
echo "前端将使用端口: $NEXT_PORT"

# 启动后端应用
cd /app/backend
export PORT=$PORT
./vps-monitor-backend &
BACKEND_PID=$!
echo "后端启动,PID: $BACKEND_PID"

# 启动Next.js应用
cd /app/frontend
export PORT=$NEXT_PORT
node server.js &
FRONTEND_PID=$!
echo "前端启动,PID: $FRONTEND_PID"

# 监听子进程,如果任何一个退出,则退出容器
wait $BACKEND_PID $FRONTEND_PID

github actions示例

name: 构建并推送Docker镜像

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: 检出代码
      uses: actions/checkout@v4

    - name: 设置Go环境
      uses: actions/setup-go@v5
      with:
        go-version: '1.23'

    - name: 设置Node.js环境
      uses: actions/setup-node@v4
      with:
        node-version: '22'
        cache: 'npm'
        cache-dependency-path: web/package-lock.json
    
    - name: 构建后端
      run: |
        go mod download
        # 编译x86_64版本
        CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -tags timetzdata -o vps-monitor-backend-amd64 .
        # 编译arm64版本
        CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -tags timetzdata -o vps-monitor-backend-arm64 .

    - name: 构建前端
      run: |
        cd web
        # 安装依赖并构建(使用standalone模式)
        npm ci
        npm run build
        # 检查standalone构建文件是否生成成功
        if [ ! -d ".next/standalone" ]; then
          echo "Next.js standalone构建失败,缺少standalone目录"
          exit 1
        fi

    - name: 设置Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: 登录到Docker Hub
      if: github.event_name != 'pull_request'
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKER_HUB_USERNAME }}
        password: ${{ secrets.DOCKER_HUB_TOKEN }}

    - name: 构建并推送Docker镜像
      uses: docker/build-push-action@v6
      with:
        context: .
        push: true
        platforms: linux/amd64,linux/arm64
        tags: ${{ secrets.DOCKER_HUB_USERNAME }}/vps-monitor:latest
        # 添加构建参数
        build-args: |
          BUILD_DATE=${{ github.event.repository.updated_at }}
          VCS_REF=${{ github.sha }}
    - name: 部署到服务器
      uses: appleboy/ssh-action@master
      env:
        DOCKER_IMAGE: ${{ secrets.DOCKER_HUB_USERNAME }}/vps-monitor:latest
        PROJECT_PATH: ${{ secrets.PROJECT_PATH }}
      with:
        host: ${{ secrets.SERVER_IP }}
        username: root
        key: ${{ secrets.SERVER_SSH_KEY }}
        envs: DOCKER_IMAGE,PROJECT_PATH
        script: |
          docker pull $DOCKER_IMAGE
          cd $PROJECT_PATH
          docker compose down
          docker compose up -d