好似没有封面

Next.js+Strapi应用使用Docker+Nginx部署详细流程

·
30 分钟阅读

前言

介绍如何使用docker去部署next.js以及strapi应用,并涉及到一些初级的自动化流程,写给一些应用部署的小白,可以参考流程一步步实现个人应用的部署,其他应用的简易部署流程也是类似的,后续将进一步探索CICD及更加高效的流程。

服务器及镜像打包

服务器初始化

首先需要到云服务商购买云服务器和域名,我使用的服务器实例是阿里云轻型应用服务器,系统是ubuntu 20.04.1

  1. 使用root用户登录服务器,添加用户并给与sudo权限。
adduser <username>
usermod -aG sudo <username>
#登录用户并验证权限
su - <username>
sudo ls /root
  1. 配置服务器免密登录 首先在你的任意linux终端添加ssh配置,vim ~/.ssh/config,配置格式如下。然后执行ssh-copy-id <快捷登录名>,输入密码即可。
Host <快捷登录名>
HostName <服务器公共ip>
User <用户名>
  1. 服务器安装docker
sudo apt-get update
sudo apt-get install docker.io
sudo apt-get install docker-compose

docker镜像打包

  1. next.js应用DockerFile, 以下是官方的Dockerfile,可以根据需要自行修改,执行docker build -i -t nextapp:v0.1 .构建镜像。
# Dockerfile

FROM node:18-alpine AS base

# 1. Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat git

WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .git ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i; \
  else echo "Lockfile not found." && exit 1; \
  fi


# 2. Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# This will do the trick, use the corresponding env file for each environment.
COPY .env.production.sample .env.production
RUN npm run build

# 3. Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

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

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static


USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME 0.0.0.0

CMD ["node", "server.js"]
  1. strapi镜像打包,需要注意的是使用Dockerfile是会报错的需要修改主机的版本,具体原因是strapi构建依赖了sharp这个库,最新的alpine中的一些c++依赖不兼容目前版本的sharp,或许您看到文章的时候已经修复了,如果遇到了一些问题,可以使用以下Dockerfile去打包尝试一下。
  2. 数据库及nginx镜像:我在项目中使用了mysql数据库,mysql和nginx的镜像都是用的官方的镜像,需要注意的是一些初始化的环境变量值,可以参考下面的docker-compose文件。 还有一个可能遇到的问题是strapi数据库连接失败的问题,很有可能是因为使用mysql8.0以上的版本,不支持mysql_native_password这种认证方式,解决方案参考官方文档,将版本替换为mysql2,然后exec进容器修改认证方式ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your password'
  3. docker-compose参考,注意在同级目录下创建.env文件。ps:由于本人也是这块的初学者,仅供参考,欢迎大佬指正交流学习。
#docker-compose.yml
version: "3"
services:
  nginx:
    container_name: nginx
    image: nginx:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    networks:
      - blog-net
    volumes:
      - /home/xh777/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
      - /home/xh777/nginx/conf.d:/etc/nginx/conf.d
      - /home/xh777/nginx/logs:/var/log/nginx
      - /home/xh777/nginx/cert:/etc/nginx/cert
    command: ["/bin/sh", "-c", "nginx -g 'daemon off;'"]

  nextblog:
    container_name: nextblog
    image: xh7777/nextblog:v0.1
    restart: unless-stopped
    ports:
      - "3000:3000"
    networks:
      - blog-net
    env_file:
      - .env
    environment:
      NODE_ENV: production
      NEXT_PUBLIC_STRAPI_API_URL: ${NEXT_PUBLIC_STRAPI_API_URL}
      PORT: ${NEXT_PORT}
      HOSTNAME: ${NEXT_HOST}
      NODE_ENV: ${NODE_ENV}
      OAUTH_CLIENT_ID: ${OAUTH_CLIENT_ID}
      OAUTH_CLIENT_SECRET: ${OAUTH_CLIENT_SECRET}
      NEXTAUTH_URL: ${NEXTAUTH_URL}
      NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
      NEXT_PUBLIC_GOOGLE_ID: ${NEXT_PUBLIC_GOOGLE_ID}

  strapi:
    container_name: strapi
    image: xh7777/strapidev:v1.2
    restart: unless-stopped
    ports:
      - "1337:1337"
    networks:
      - blog-net
    env_file:
      - .env
    volumes:
      - strapi-upload:/opt/app/public/uploads
    environment:
      MYSQL_USER: ${DATABASE_USERNAME}
      MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_DATABASE: ${DATABASE_NAME}
      HOST: ${STRAPI_HOST}
      PORT: ${STRAPI_PORT}
      APP_KEYS: ${APP_KEYS}
      API_TOKEN_SALT: ${API_TOKEN_SALT}
      ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
      TRANSFER_TOKEN_SALT: ${TRANSFER_TOKEN_SALT}
      DATABASE_CLIENT: ${TRANSFER_TOKEN_SALT}
      DATABASE_HOST: ${DATABASE_HOST}
      DATABASE_PORT: ${DATABASE_PORT}
      DATABASE_NAME: ${DATABASE_NAME}
      DATABASE_USERNAME: ${DATABASE_USERNAME}
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
      JWT_SECRET: ${JWT_SECRET}
      # NODE_ENV: ${NODE_ENV_DEV}
    depends_on:
      - strapiDB

  strapiDB:
    container_name: strapiDB
    restart: unless-stopped
    image: mysql:latest
    command: --default-authentication-plugin=mysql_native_password
    env_file:
      - .env
    environment:
      MYSQL_USER: ${DATABASE_USERNAME}
      MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_DATABASE: ${DATABASE_NAME}
    volumes:
      - blog-data:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - blog-net

volumes:
  blog-data:
  strapi-upload:
networks:
  blog-net:
    driver: bridge
  1. .env文件参考
#.env.example
#DB config
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_NAME=
#NEXT config
NEXT_PUBLIC_STRAPI_API_URL=
NEXT_HOST=
NEXT_PORT=
OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
NEXTAUTH_URL=
NEXTAUTH_SECRET=
NEXT_PUBLIC_GOOGLE_ID=

#STRAPI config
STRAPI_PORT=
STRAPI_HOST=
APP_KEYS=
API_TOKEN_SALT=
ADMIN_JWT_SECRET=
TRANSFER_TOKEN_SALT=
DATABASE_CLIENT=
DATABASE_HOST=
DATABASE_PORT=
JWT_SECRET=
#public
NODE_ENV=production
NODE_ENV_DEV=dev

配置GITHUB ACTION CI

注册docker hub账号

前往Dockerhub官网注册账号并登录。Docker hub是官方的docker镜像托管平台,就是一个镜像专属的github,我们可以随时随地的将自己的镜像传到上面去。然后供服务器拉取最新的镜像以更新容器。

配置github action

github action是一款免费的ci工具,托管在github上的项目可以十分方便的利用它的功能,我们可以在自己的项目主页看到action一栏,点进去可以查看详情。
我们具体要做的事情就是省去我们自己手动将应用构建打包,跑测试,然后将代码scp到远程服务器上的这一过程。这些过程都可以由action自动完成。
首先需要了解的是github action的一些基本概念:

  • workflow (工作流程):持续集成一次运行的过程
  • job (任务):一个 workflow 由一个或多个 job 构成,表示一次持续集成的运行,可以完成多个任务
  • step(步骤):每个 job 由多个 step 构成,一步步完成
  • action (动作):每个 step 可以依次执行一个或多个命令(action)

action的一般流程如下:

  • 在项目目录下创建.github/workflow文件夹,编写对应的yml文件
  • 执行设置的git操作如merge、push等,自动触发工作流
  • 查看执行结果

以下是一个自动打包镜像并发布到dockerhub的工作流,

# .github/workflow/docker-image.yml
name: Docker Image CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      -
        name: Checkout
        uses: actions/checkout@v4
      -
        name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_NAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      -
        name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      -
        name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: xh7777/nextblog:v0.1.6

       

上面的工作流触发的时机是当代码提交或合并到主分支时,然后执行初始化,第三方登录,镜像打包,镜像上传的过程。上面的用户名密码是通过环境变量引入的,具体可以在github项目目录中的settings/Security/Secrets and variables/action里边设置环境变量。

服务器操作

以上步骤都完成后,需要完成项目在服务器上的部署。首先在服务器上登录dockerhub,执行docker login命令。将docker-compose.yml和.env文件scp到项目根目录下,执行docker-compose up -d启动容器。最后配置nginx。后续版本更新和回滚只要修改docker-compose.yml中镜像的版本号,然后重新执行命令即可,这些过程也是可以自动化的,后续将继续学习。
以上内容是我在部署个人应用踩得很多坑和基于很多文章后的实践,还有很多需要学习思考的地方,希望以后在企业中能学习到更多。