主要区别是使用前端编译后的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