Next.js+Strapi应用使用Docker+Nginx部署详细流程
- 30 分钟阅读
前言
介绍如何使用docker去部署next.js以及strapi应用,并涉及到一些初级的自动化流程,写给一些应用部署的小白,可以参考流程一步步实现个人应用的部署,其他应用的简易部署流程也是类似的,后续将进一步探索CICD及更加高效的流程。
服务器及镜像打包
服务器初始化
首先需要到云服务商购买云服务器和域名,我使用的服务器实例是阿里云轻型应用服务器,系统是ubuntu 20.04.1
。
- 使用root用户登录服务器,添加用户并给与sudo权限。
adduser <username>
usermod -aG sudo <username>
#登录用户并验证权限
su - <username>
sudo ls /root
- 配置服务器免密登录 首先在你的任意linux终端添加ssh配置,
vim ~/.ssh/config
,配置格式如下。然后执行ssh-copy-id <快捷登录名>
,输入密码即可。
Host <快捷登录名>
HostName <服务器公共ip>
User <用户名>
- 服务器安装docker
sudo apt-get update
sudo apt-get install docker.io
sudo apt-get install docker-compose
docker镜像打包
- 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"]
- strapi镜像打包,需要注意的是使用Dockerfile是会报错的需要修改主机的版本,具体原因是strapi构建依赖了sharp这个库,最新的alpine中的一些c++依赖不兼容目前版本的sharp,或许您看到文章的时候已经修复了,如果遇到了一些问题,可以使用以下Dockerfile去打包尝试一下。
- 数据库及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'
- 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
- .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中镜像的版本号,然后重新执行命令即可,这些过程也是可以自动化的,后续将继续学习。
以上内容是我在部署个人应用踩得很多坑和基于很多文章后的实践,还有很多需要学习思考的地方,希望以后在企业中能学习到更多。