kasuie page cover

Docker+Next.js+Spring boot+Github actions实现CI/CD

发表于

浏览量816

评论数1

Docker compose+Next.js+Spring boot+Prisma+Github actions实现CI/CD

其实主要是记录自己部署博客系统的流程,也是自己慢慢摸索出来的,踩的坑数不胜数,也仅供参考📑

使用Next.js,主要是用于SEO,因此不要问我为什么还要用Spring Boot,问就是懒,后端服务是现成的,所以并没有打算用Next.js完全重写后端服务,但瞎搞了个对象关系映射框架PrismaNextAuth.jsNextAuth.js实现了鉴权功能,搭配了Dcoker使用了其容器编排功能,再使用Github Actions,从推送代码后,就完全交由其,从打包,制作镜像,上传镜像,拉取镜像,重新部署......不再需要自己手动了,帮助节省了很多在部署和维护项目上的时间。

Docker compose

Docker compose(容器编排技术以下简称compose)之前,你可能需要先了解一下 DockerDocker ,它是一个软件平台,让您可以快速构建、测试和部署应用程序。Docker 将软件打包成名为container(容器)的标准化单元,这些单元具有运行软件所需的所有功能,包括库、系统工具、代码和运行时。使用 Docker,您可以将应用程序快速部署和扩展到任何环境中。而compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 compose您可以使用yml文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 yml 文件配置中创建并启动所有服务。

可能说的很抽象,有个很形象的比喻:在一个酒馆里,酒就是镜像,酒杯就是容器,而负责管理这些酒杯的就是compose了

大致了解这些,接下来才好理解,以下是我的compose配置文件,一共定义了MySQLRedisWeb-api(Spring boot 后端应用)Web-v4(Next.js 应用)四个服务:

# 指定 Docker Compose 文件的版本,根据自己安装的docker版本而定
version: "3"
# 定义一个自定义的网络kasuie,用于连接这些服务,网络类型为 bridge (Docker 默认的网络模式)
networks:
  kasuie:
    driver: bridge

services:
  database: # 定义一个 MySQL 服务
    image: mysql:5.7.36 #  指定镜像及版本,如果镜像在本地不存在,会尝试去拉取这个镜像
    container_name: mysql # 指定容器的名称为 mysql
    ports: # 宿主端口:容器端口 格式,这里将容器的 3306 端口映射到宿主机(所在服务器)的 3306 端口,可映射多个端口
      - "3306:3306"
    environment: # 设置容器的环境变量,包括时区、MySQL root 用户的访问权限和密码
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_HOST=%
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
    volumes: # 挂载宿主机上的文件或目录到容器内,包括 MySQL 配置文件和数据目录,避免重启数据丢失
      - /etc/my.cnf:/etc/my.cnf
      - /var/lib/mysql/:/var/lib/mysql/
      - /home/mysql/initdb.sql:/docker-entrypoint-initdb.d
    restart: always # 设置容器总是在退出时重新启动
    command: # 指定运行容器时执行的命令,配置 MySQL 服务的一些参数
      - mysqld 
      - --default-storage-engine=InnoDB 
      - --character-set-server=utf8mb4 
      - --collation-server=utf8mb4_unicode_ci 
      - --explicit_defaults_for_timestamp
    networks: # 将容器连接到 kasuie 网络
      - kasuie
  redis: # 定义一个 Redis 服务,相同字段解释同上
    image: redis:7.0.4
    container_name: redis
    environment:
      TZ: Asia/Shanghai
    command: redis-server --save 600 1000 --requirepass ${REDIS_PASSWORD} --port ${REDIS_PORT}
    restart: always
    volumes:
      - /usr/local/docker/redis:/data #数据文件挂载
      - /usr/local/docker/redis/redis.conf:/etc/redis/redis.conf #配置文件挂载
    networks:
      - kasuie
  web-api: # 定义一个 web-api 服务
    image: ${APITAG:-default} # 使用镜像由变量 `${APITAG}` 指定,不指定默认为default
    restart: always
    container_name: web-api
    environment: # 设置容器的环境变量,包括 MySQL 和 Redis 的连接信息
      MYSQL_URL: ${MYSQL_URL}
      REDIS_HOST: ${REDIS_HOST}
      REDIS_PORT: ${REDIS_PORT}
      REDIS_PASSWORD: ${REDIS_PASSWORD}
      TZ: Asia/Shanghai
    ports:
      - "8001:8001"
    networks:
      - kasuie
    depends_on: # 定义此服务依赖于其他服务(database 和 redis),确保在启动此服务之前启动这两个服务
      - database
      - redis
  web-v4:
    image: ${IMAGETAG:-default}
    restart: always
    container_name: web-v4
    ports:
      - "3001:3000"
    networks:
      - kasuie

在配置文件同目录下有.env的环境变量配置文件:

MYSQL_ROOT_PASSWORD=your_mysql_password
MYSQL_URL=your_mysql_url
REDIS_PASSWORD=your_mysql_password
REDIS_PORT=your_redis_port
REDIS_HOST=redis

第一次需要手动在服务器启动一下,compose的启动命令: docker-compose up -d-d表示在后台启动,当然这里有点不一样的是,不能直接全部启动,观察一下上面的变量是否缺少了些什么?

注意: docker-compose up -d 命令是找的当前目录下docker-compose.yml 文件,请注意配置文件名,如果不是这个文件名,你可以通过-f指定配置文件,例如docker-compose -f /root/myfile.yml up -d

细心一点你会发现,在环境变量文件中,只有mysqlredis相关的,但compose配置文件中还存在${IMAGETAG}${APITAG}两个关于镜像的变量,这是没有提到并缺少的,所以直接运行启动命令是不会成功的。而之所以这样写是因为web-apiweb-v4这两个的镜像是我自己的维护的服务,也就是我的博客整个系统,会经常更新,每一次通过Github actions推送代码都会自动打包一个唯一的镜像并推送到镜像仓库,然后这两个变量会在Github actions的工作流程中使用到,后续会详细讲这个部分。

简略一点来说,这样写是为了每一次都拉取自己最新维护的镜像版本,然后第一次启动因为你两个服务的镜像仓库都是空的,自然不能全部运行(如果你仓库有,可以手动通过export设置这里的变量后,就可以启动),其中mysqlredis是官方镜像,所以可以启动,第一次只启动它们就可以了,运行:

docker-compose up -d database redis

到这里compose这部分手动需要我们操作的这部分就算是完成了.

Github actions

然后要实现CI/CD,就必须要说说Github actionsGithub actions了,它是Github一种持续集成和持续交付 (CI/CD) 平台,可用于自动执行生成、测试和部署管道,像什么检入检出代码、运行测试打包、登录远程服务器,发布到第三方服务等等,Github把这些操作就称为 actions,如果你需要某个 action,不必自己写复杂的脚本,直接引用他人写好的action 即可,你可以在Github提供的官方市场官方市场找到,整个持续集成过程,就变成了一个 actions 的组合,而最终会形成一个workflow(工作流程),那么项目如何使用呢?

Github actions的工作流程在项目根目录下的 .github/workflows 目录中定义,这些定义文件叫做workflow文件,可以有多个工作流程文件,每个工作流程都可以执行不同的任务集,采用 yaml 格式,文件名可以任意的,但是后缀必须是.yml,这样才能识别到。

Next.js中的Workflows

例如,我的Next.jsNext.js项目,在 .github/workflows/actions.yml目录新建名为actions.yml 的文件,代码如下:

name: Web-v4 Docker Image CI # workflow 的名,可省略,默认为当前 workflow 的文件名
on: # 指定触发 workflow 的条件,这里是指定代码push的时候,可为数组,指定多个条件:[push, pull_request],push或者pull_request都可以触发
  push:
    branches: [pro-docker] # 指定workflow工作的分支,可以填多个
  workflow_dispatch: # 可在Github actions面板手动触发workflow

#  一个workflow由执行的一项或多项job(任务)
# 一个job包含一个或多个setp(步骤),steps字段指定每个job的运行步骤
jobs: 
  build-and-push:  # 指定这个job名为build-and-push,Github actions面板流程图会显示这个名称
    runs-on: ubuntu-latest # 指定运行在最新版ubuntu系统中
    steps:
      - name: Set state # 指定这个setp名为Set state,job面板详情会显示
        run: | # 执行某个shell命令或脚本
	  echo "DOCKER_NAMESPACE=kasuie" >> $GITHUB_ENV # 设置一些变量并存储在 GitHub actions 的环境变量中,以便在后续步骤中可以使用
          echo "IMAGE_NAME=image-web-v4" >> $GITHUB_ENV
          echo "IMAGE_TAG=registry.cn-hangzhou.aliyuncs.com/kasuie/web-v4:${{github.sha}}" >> $GITHUB_ENV
      - name: Checkout # 使用actions/checkout@v4检出代码
        uses: actions/checkout@v4
      - name: Login to Ali Docker 
        uses: aliyun/acr-login@v1 # 因为我用的是阿里云的镜像服务,所以使用了阿里云的aliyun/acr-login@v1登陆阿里云docker仓库
        with:
          login-server: ${{secrets.ALI_DOCKER_REGISTRY}}
          username: ${{secrets.ALI_DOCKER_USERNAME}}
          password: ${{secrets.ALI_DOCKER_PASSWORD}}
      - name: Build and push Docker Image 
        uses: docker/build-push-action@v5 # 使用docker/build-push-action@v5构建docker镜像,并上传到阿里云镜像仓库
        with:
          file: ./Dockerfile # 放在根目录下的Dockerfile文件,后面会讲,包含一组用于在 Docker 容器中自动执行的指令,指定构建自定义Docker镜像的步骤
          push: true # 是否自动推送
          tags: ${{env.IMAGE_TAG}} # 镜像的tag
  pull-and-restart:  # 指定这个job名为pull-and-restart
    needs: build-and-push # 指定当前任务的依赖关系,即运行顺序,这里是这个job必须等待 build-and-push 成功完成才执行
    runs-on: ubuntu-latest
    steps:
      - name: Set state
        run: |
	  echo "DOCKER_NAMESPACE=kasuie" >> $GITHUB_ENV
          echo "IMAGE_NAME=image-web-v4" >> $GITHUB_ENV
          echo "IMAGE_TAG=********/****/****:${{github.sha}}" >> $GITHUB_ENV
      - name: Login Server and Start
        uses: appleboy/ssh-action@master # 使用appleboy/ssh-action@master登录远程服务器并重启服务
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USERNAME }}
          key: ${{ secrets.PRIVATE_KEY }} 
          script: | # shell命令,这里是服务器上拉取镜像,删除旧版本镜像,以及重启对应容器服务
	    cd /usr/local/web/
            docker login -u=${{secrets.ALI_DOCKER_USERNAME}} -p=${{secrets.ALI_DOCKER_PASSWORD}} ${{secrets.ALI_DOCKER_REGISTRY}}
            docker-compose stop web-v4 && docker-compose rm web-v4
            docker image rm $(docker image ls *镜像仓库地址* -q) || echo "删除镜像异常"
            export IMAGETAG=${{env.IMAGE_TAG}}
            docker-compose up -d web-v4

可以看到最后的shell命令,使用export 设置了环境变量IMAGETAG,也就是在上文compose配置文件提到的环境变量,可以看到它是取的${{env.IMAGE_TAG}},而IMAGE_TAG又是我每一次的运行工作流程都会写入到环境变量的echo "IMAGE_TAG=********/****/****:${{github.sha}}" >> $GITHUB_ENV,其中的${{github.sha}}是github对于每一次commit(提交)生成的唯一标识,这样就可以把最新的镜像通过action在服务器进行拉取和重新部署。

而其他例如${{secrets.ALI_DOCKER_REGISTRY}}${{secrets.REMOTE_HOST}} 等等是GitHub actions工作流程的变量,这里是把镜像仓库信息和服务器信息设置成变量,你可以在对应仓库的Settings面板新增:

new actions

需要注意的是PRIVATE_KEY ,它是你服务器的SSH私钥,是你登录服务器的凭证,第一次需要你在服务上面生成,首选运行ssh-keygen,根据自己情况选择,最后在~/.ssh/目录里面生成如下文件:

ssh

id_rsa是私钥文件,id_rsa.pub是公钥文件,然后~/.ssh/目录下依次运行:

cat id_rsa.pub >> authorized_keys  # 复制一份公钥到authorized_keys中
chmod -R 700 ~/.ssh # 修改文件权限
chmod -R 640 authorized_keys # 修改文件权限

若文件或目录不存在,可以自己创建,最后执行cat id_rsa获得私钥内容,这就是这里需要填写的PRIVATE_KEY

以上workflow 配置文件中基本每一个命令我都注释了,虽然并不是特别复杂,但以此可大致的窥得全貌,然后就是Spring boot中。

Spring Boot中的Workflows

Next.js相比,Spring boot中除构建环节不一样,其他步骤几乎一样,以下是具体的actions.yml 的文件:

name: Web2 Docker Image CI
on:
  push:
    branches: [master]
  workflow_dispatch:

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    name: Running and compile
    steps:
      - name: Set state
        run: |
	  echo "IMAGE_TAG=${{secrets.DOCKER_REPOSITORY}}:${{github.sha}}" >> $GITHUB_ENV
      - name: Checkout
        uses: actions/checkout@v4
      - name: Login to Ali Docker
        uses: aliyun/acr-login@v1
        with:
          login-server: ${{secrets.ALI_DOCKER_REGISTRY}}
          username: ${{secrets.ALI_DOCKER_USERNAME}}
          password: ${{secrets.ALI_DOCKER_PASSWORD}}
      - name: Build and push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{env.IMAGE_TAG}}
  pull-and-restart:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Set state
        run: |
	  echo "IMAGE_TAG=${{secrets.DOCKER_REPOSITORY}}:${{github.sha}}" >> $GITHUB_ENV
      - name: Login Server and Start
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USERNAME }}
          key: ${{ secrets.PRIVATE_KEY }}
          script: |
	    cd /usr/local/web/
            docker login -u=${{secrets.ALI_DOCKER_USERNAME}} -p=${{secrets.ALI_DOCKER_PASSWORD}} ${{secrets.ALI_DOCKER_REGISTRY}}
            docker-compose stop web-api && docker-compose rm web-api
            docker image rm $(docker image ls *镜像仓库地址*  -q) || echo "删除镜像异常"
            export APITAG=${{env.IMAGE_TAG}}
            docker-compose up -d web-api

可以看到几乎和Next.js中的步骤一样,所以不再过多做说明,不同的地方在Dockerfile文件中,所以下面就讲讲构建镜像了。

构建镜像

Dockerfile是用于构建 DockerDocker 镜像的文本文件,其中包含了一系列的指令和配置,用于定义容器镜像的构建规则和运行环境,通过它,你可以定义容器的内容、配置和运行时行为,使得容器的构建和部署过程变得自动化和可重复(当然你可以不使用容器部署,就不用这么麻烦,只需要在compose文件将build产物放到服务器,执行node server.js就可以了)。

Next.js构建镜像

以下是我的Dockerfile文件:

# 指定基础镜像为Node.js 18 版本的 Alpine Linux 并命名为base,以后可以通过这个名称引用。
FROM node:18-alpine AS base 
# 从上一阶段的 base 镜像继续构建,创建一个新的镜像阶段,命名为 deps。
FROM base AS deps
# 安装 Alpine Linux 上的一个软件包libc6-compat,用于兼容性
RUN apk add --no-cache libc6-compat

# 指定工作目录为 /web-v4
WORKDIR /web-v4
# 将本地的 package.json、yarn.lock、package-lock.json、pnpm-lock.yaml 文件以及 /prisma`目录复制到容器中的 /web-v4 目录
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
COPY /prisma ./
# 安装依赖,sharp 是一个用于处理图像的库,我这里貌似配置不当,生成环境sharp老是报错,这里可以忽略掉关于它的命令,不安装也不会导致失败,只是nextjs应用的图形处理无法使用,然后,使用 npm ci`安装依赖,如果安装失败,则删除 node_modules 目录并退出构建
RUN set -eux; \
    npm i sharp \
    npm ci || { rm -rf node_modules; exit 1; };
# 如果你使用想要兼容其他包管理工具,以下可作参考
# RUN npm i sharp
# RUN \
  # if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  # elif [ -f package-lock.json ]; then npm ci; \
  # elif [ -f pnpm-lock.yaml ]; then npm install -g pnpm && pnpm i --frozen-lockfile; \
  # else echo "Lockfile not found." && exit 1; \
  # fi
# 

# 从 base 镜像创建一个新的构建阶段,命名为 builder
FROM base AS builder 
#指定工作目录
WORKDIR /web-v4
# 从之前的 deps 阶段复制依赖到当前阶段,然后将当前目录中的所有文件复制到容器的 /web-v4 目录
COPY --from=deps /web-v4/node_modules ./node_modules
COPY --from=deps /root/.npm /root/.npm
COPY . .

#开始构建
RUN npm run build

# 从 base 镜像创建一个新的运行阶段,命名为 runner
FROM base AS runner
WORKDIR /web-v4
# 添加一个系统用户组和用户,用于运行应用程序
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# 创建 .next 目录,并设置所有者为新创建的用户和用户组
RUN mkdir .next
RUN chown nextjs:nodejs .next
# 设置环境变量 NODE_ENV 为 production,表示应用程序运行在生产环境下。同时,设置 NEXT_SHARP_PATH`变量用于防止找不到 `sharp` 
ENV NODE_ENV production
ENV NEXT_SHARP_PATH /wev-v4/node_modules/sharp
# 从之前的 builder 阶段复制构建好的静态文件和应用程序到当前阶段
COPY --from=builder /web-v4/public ./public
COPY --from=builder --chown=nextjs:nodejs /web-v4/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /web-v4/.next/static ./.next/static

# 切换用户到 nextjs 用户
USER nextjs

#可以指定运行的端口,next应用默认是3000
# ENV PORT 3000
# 这里如果不是使用docker compose,容器运行成功但访问不了可以尝试把HOSTNAME 设置为 “localhost”
# ENV HOSTNAME "localhost"

# 定义容器启动时运行的默认命令,这里是运行 server.js 文件
CMD ["node", "server.js"]

详细的注释我也写上了,大致是这样的,在Github actions中使用该配置构建后然后就可以上传到镜像仓库了。

搭配Prisma

PrismaPrisma是一个功能强大的数据库工具和 ORM 框架,在之前的文章中有提到:Next.js 踩坑记录Next.js 踩坑记录,这里多做介绍了。

如果你要在项目中进行使用,依次执行如下命令

npm install prisma --save-dev # 安装依赖
npx prisma init # 初始化

初始化之后会在根目录下创建一个 .env (如果没有的话)文件和一个 prisma 目录,.env 文件用于定义环境变量(例如数据库连接),prisma 目录下生成了一个 schema.prisma 文件,schema.prisma 文件包含带有数据库连接变量和模式模型的 prisma 模式

.env文件定义数据库连接变量:DATABASE_URL="mysql://username:mysql_password@localhost:3306/db_name?schema=publicschema.prisma文件中使用该环境变量:

prisma

我用的是Mysql,当然你也可以选择其他的,截止我写这篇文章为止它还支持PostgreSQLMongoDBSQL ServerSQLite,如果你不是Mysql需要改动一下provider = "mysql",改成自己使用的数据库即可。 在`

设置完成并且没有问题后,如果使用的数据库中已有表,这个时候可以运行 npx prisma db pull,通过 这个prisma 的命令来帮我们查询数据库,并且在schema.prisma文件中自动生成数据模型定义描述,可以看到很多的model,这就是数据库中的表的映射模型。

model

当然你可能新建的数据库,如果是以schema.prisma文件为切入点,想要将变更同步到数据库,那么你可以在schema.prisma文件中手动新建model,也就是数据库表,类似于上图一样的格式,不过手动可能需要一点基础知识,建好之后,执行命令npx prisma db push,执行之后会自动检测数据模型文件的变更,并应用这些变更到数据库中。

到这里数据库的连接和定义描述算是没有问题了,接下来需要安装并生成prisma客户端,这样才能和数据库进行CRUD,通过 Prisma Client 来进行数据库交互,执行命令 npm install @prisma/client 。由于 Prisma Client是根据您的schema定制的,因此每次 schema.prisma 文件发生更改时,您都需要通过运行prisma generate来更新它,当你安装@prisma/client时,他会自动执行prisma generate,因此首次并不需要执行prisma generate

接下来是使用,你需要创建一个 PrismaClient 实例,可以将其导入到任何需要的文件中使用该实例,如下:

// lib/prisma.ts
import { PrismaClient } from "@prisma/client";

declare global {
  var prisma: PrismaClient | undefined;
}

const prisma = global.prisma || new PrismaClient();

if (process.env.NODE_ENV === "development") global.prisma = prisma;

export default prisma;

然后就可以愉快的使用了~ 在需要的地方导入import prisma from "@/lib/prisma"; 然后使用,例如我查询一个用户:

await prisma.t_user.findUnique({
	where: {
		password: Encrypt(password),
		username,
	},
});

到这里就可以进行基本的食用了,感兴趣可以进一步了解

最后需要注意的就是构建Next.js镜像的时候,要把schema.prisma也复制出来(参考上面的dockerfile文件),如果出现prisma的报错,有可能你需要在build之前,加上命令 RUN npx prisma generate,保证是正确的Prisma Client,还有就是因为Mysql是使用的容器,因此本地开发和构建的时候数据库连接会有所变化,应使用docker compose的服务名连接,例如DATABASE_URL="mysql://username:mysql_password@mysql:3306/db_name?schema=public"

Spring Boot构建镜像

Spring boot构建相较于Next.js稍微简单一点,以下是我的Dockerfile文件:

# 使用 Maven 3.6.3 和 OpenJDK 8 作为构建阶段的基础镜像。这个阶段用于构建应用程序
FROM maven:3.6.3-openjdk-8 AS build
# 设置工作目录为 /web-api
WORKDIR /web-api

# 将项目的 pom.xml`文件和 src 目录拷贝到容器的 /web-api 目录
COPY pom.xml .
COPY src ./src

# 运行 Maven 命令,执行项目的 `clean` 和 `package` 阶段,将构建的 jar 文件从 /web-api/target/ 目录复制到 /web-api 目录,并重命名为 web-api.jar
RUN mvn clean package \
    && cp /web-api/target/*.jar web-api.jar

# 使用 OpenJDK 8 的 JRE 版本作为最终镜像的基础镜像
FROM openjdk:8-jre-alpine
# 设置最终镜像的工作目录为 /web-api
WORKDIR /web-api
# 从前一个构建阶段 (`build`) 中复制构建好的 web-api.jar 文件到最终镜像的当前目录
COPY --from=build /web-api/web-api.jar ./

# 设置 Java 虚拟机的启动参数,包括 `-server` 表示使用服务器模式,以及设置为初始堆大小(-Xms)和最大堆内存(-Xmx)
ENV JAVA_OPTS="-server -Xms256m -Xmx512m"

# 暴露容器的 8001 端口,表示该容器应用程序监听在该端口上
EXPOSE 8001

设置容器启动时执行的默认命令
ENTRYPOINT ["java","-jar", "web-api.jar"]

# 设置镜像的作者信息
MAINTAINER kasuie

它们打包需要的基础镜像不一样,后面的打包步骤也不一样,使用该配置构建后上传到镜像仓库

需要注意的是,Spring boot项目里面连接MysqlRedis的信息是在Docker compose配置文件通过环境变量注入的,需要配合进行修改,如果设置不当,会导致数据库连接失败。

sql

至此,两个镜像的自动化部署时的构建过程就算是说完了。

MySql 和 Redis

这部分是题外话,不讲具体的过程了,反正也是用的官方镜像,只是简单说一下我在部署的时候,遇到的几个坑,一个是MysqlRedis连接不上,这个需要注意在运行的时候用服务名+端口连接,还有统一各个服务运行时的时区,另一个是进行数据数据持久化的时候,注意一下文件的权限,有可能服务没权限导致出问题。

最后

放一下自己的actions面板:

fail

❗一共失败了 67 次,这还只是Next.js的部分,还不包括后端服务,至少尝试了100次,有些成功了也未必算成功。

总之以上都是自己一步一步踩坑得到的,当然也并不代表完全正确,根据实际情况看吧,或者也有错误地方,欢迎指正~

可以看到我前端项目名是web-v4,是的现在又重构了一个版本了,之前是Next.jspages路由,现在是app路由,以前如果我要部署的话,大概流程是先在本地打包,打包后,需要将生产文件上传至服务器解压缩到对应文件夹,服务器停止对应服务,必要的话需要删除旧的文件夹,里面包含文件很多删除都挺费时间的,最好再重启,来回折腾的话大概十分钟起步吧。使用自动化部署,将为你节省这十分钟,让你可以干其他事情,还是蛮不错的👏。

尾巴大爷
  • 地点: 成都
  • 心情:自由的小猫在冬天会被冻死,有点不喜欢冬天了

留下你的评论吧

http(s)://

回到顶部