1. 编写Dockerfile文件,将nextjs项目打包成镜像
- 首先找到nextjs的官方文档,其中有一个将nextjs项目打包成镜像的示例代码
- 找到示例代码中的Dockerfile文件,直接copy(不需要任何修改,先打包后再根据需要进行修改)
FROM node:18-alpine AS base
# 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
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
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 --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: <https://nextjs.org/telemetry>
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# 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
# server.js is created by next build from the standalone output
# <https://nextjs.org/docs/pages/api-reference/next-config-js/output>
CMD HOSTNAME="0.0.0.0" node server.js
- 执行 docker build 命令,将项目打包成 docker 镜像,在本地进行测试
docker image build -t my-first-portfolio:latest
docker镜像运行截图:
注意: 上面截图中的docker build 命令是带有环境变量参数的,最开始的时候可以不用传入
本地docker仓库截图:
- 使用docker run 命令,启动打包好的镜像,对项目进行测试
- 测试没问题,就可以进行项目自动化部署的过程了
2.阿里云docker环境安装
- 按照官方安装docker的步骤一步一步进行,没啥大问题
根据自己的服务器系统,选择对应的安装步骤
- docker安装后,只允许root用户执行docker相关命令记得添加其他用户的操作权限
# 非root用户 执行docker命令报错
# 添加用户到docker权限
sudo gpasswd -a YourUserName docker
newgrp docker
# 再次执行命令
docker ps
- 至此,阿里云服务器的Docker环境就安装成功啦
3. 初始化设置阿里云镜像仓库(这里镜像仓库可以根据自身需要来创建,可以直接使用dockerhub)
这个镜像仓库的作用就是我们用docker build构建生成的镜像推送到这个仓库,后面远程服务器的docker来拉取这个镜像,实现自动化部署
- 访问阿里云镜像服务,找到示例列表(一般购买了服务器后,实例列表里面自动会有一个个人实例),点击进入这个实例
- 在创建镜像仓库之前,需要创建一个命名空间
- 创建成功后,再在镜像仓库页面中,创建自己的镜像库,填写好相关信息后,绑定代码源
- 这样阿里云镜像仓库就创建完毕了,点击对应的仓库名称,跳转到仓库基本信息页面,这个页面有相关的docker操作指南
- 在本地测试docker login 到阿里云镜像仓库
第一次登录会失败,因为阿里云镜像仓库的密码和阿里云账号的密码是不一致的,需要在阿里云镜像仓库这边设置初始密码
- 测试登录成功后,就可以进行本地镜像推送了,我这里没有在本地测试推送,直接使用github action自动化创建镜像 并推送到阿里云镜像仓库
4. Github Action自动化部署配置
参考文档:
blog.bot-flow.com/nextjs-dock…
- 第一步就是使用github action自动创建docker镜像,并将镜像推送到阿里云镜像仓库(这里参照阿里云镜像仓库的操作文档来实现)
jobs:
build:
runs-on: ubuntu-latest
steps:
# 拉取main分支代码
- name: Checkout
uses: actions/checkout@v4.1.7
# 制作docker镜像并推送到阿里云容器镜像服务
- name: build and push docker image
run: |
echo ${{ secrets.ALIYUN_DOCKER_PASSWORD }} | docker login ${{ vars.REGISTRY_MIRROR }} --username ${{ secrets.ALIYUN_DOCKER_USERNAME }} --password-stdin
docker image build -t ${{ vars.APP_NAME }}:latest .
docker tag ${{ vars.APP_NAME }} ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
docker push ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
docker logout
echo "app name ${{ vars.REGISTRY_MIRROR }}"
其中的secrets和vars变量是在github setting中配置的, secrets是加密的,vars是明文,看个人需要添加对应的字段
- 第二步,由于执行了第一步,这个项目的docker镜像已经打包并上传到阿里云镜像仓库,可以在阿里云镜像仓库查看是否存在镜像版本
执行结果:
- 第三步,通过ssh连接到阿里云服务器,执行docker pull和docker run,实现服务的docker镜像部署启动
注意: 下面这段代码和第一步中要合并起来,注意yml文件的空格并进行格式化对齐
# 登录远程服务器,拉取镜像,制作并重启容器
# <https://github.com/marketplace/actions/remote-ssh-commands>
- name: ssh remote deploy
uses: fifsky/ssh-action@master
with:
command: |
cd /
echo -e "1.docker login start==>"
echo ${{ secrets.ALIYUN_DOCKER_PASSWORD }} | docker login ${{ vars.REGISTRY_MIRROR }} --username ${{ secrets.ALIYUN_DOCKER_USERNAME }} --password-stdin
echo -e "2.docker stop myblog container==>"
docker container stop ${{ vars.APP_NAME }}
echo -e "3.docker conatainer rm==>"
docker container rm ${{ vars.APP_NAME }}
echo -e "4.docker image rm==>"
docker image rm ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
echo -e "5.docker pull==>"
docker pull ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
echo -e "6.docker container create and start==>"
docker container run -d -p 3000:3000 --name ${{ vars.APP_NAME }} ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
echo -e "7.docker logout==>"
docker logout
host: ${{ secrets.HOST }}
user: ${{ secrets.USER }}
pass: ${{ secrets.PASSWORD }}
执行结果:
- 至此,Next JS项目使用docker完成镜像打包和自动化部署就大功告成了,我们把代码合进main分支就会触发github action构建,完成自动化部署
验证部署是否成功:
5.NextJS Production环境变量注入
如果创建一个.env.production 文件,将需要的环境变量进行写入并保存,在本地进行docker build和docker run命令,我们可以发现环境变量能够正常注入到打包的镜像中,Next JS应用的一些请求能够正常发送
但是由于.env.production 文件中保存有一些敏感信息, 如果直接将.env.production 文件上传到github代码仓库中,就会导致信息泄露,所以我们这边需要采取将环境变量手动注入的方式来实现
- 第一步,修改Dockerfile文件,我们可以通过docker --build-arg来传入外部参数,在 Dockerfile 通过ARG变量来接收,然后将这些变量写入到.env.production中
完整配置如下:
FROM node:20-alpine AS base
# 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
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn config set registry <https://registry.npmmirror.com/>; \
elif [ -f package-lock.json ]; then npm config set registry <https://registry.npmmirror.com/>; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm config set registry <https://registry.npmmirror.com/>; \
else echo "Lockfile not found." && exit 1; \
fi
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 设置构建参数
ARG NOTION_TOKEN
ARG NOTION_ABOUT_DATABASE_ID
ARG NOTION_PROJECT_DATABASE_ID
ARG NOTION_EXPERIENCE_DATABASE_ID
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: <https://nextjs.org/telemetry>
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
# 导出环境变量
RUN echo "NOTION ---> "
RUN echo "NOTION_TOKEN=${NOTION_TOKEN}" >> .env.production
RUN echo "NOTION_ABOUT_DATABASE_ID=${NOTION_ABOUT_DATABASE_ID}" >> .env.production
RUN echo "NOTION_PROJECT_DATABASE_ID=${NOTION_PROJECT_DATABASE_ID}" >> .env.production
RUN echo "NOTION_EXPERIENCE_DATABASE_ID=${NOTION_EXPERIENCE_DATABASE_ID}" >> .env.production
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# 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
# server.js is created by next build from the standalone output
# <https://nextjs.org/docs/pages/api-reference/next-config-js/output>
CMD HOSTNAME="0.0.0.0" node server.js
- 本地执行docker build和docker run命令,看打包后的镜像环境变量是否正常注入(看接口请求是否成功)
docker image build -t my-first-portfolio:latest \ 54m 39s 20:37:23
--build-arg NOTION_TOKEN=YOUR ARG \
--build-arg NOTION_ABOUT_DATABASE_ID=YOUR ARG \
--build-arg NOTION_PROJECT_DATABASE_ID=YOUR ARG \
--build-arg NOTION_EXPERIENCE_DATABASE_ID=YOUR ARG \
.
docker run -p 8081:3000 my-first-portfolio
- 查看本地项目运行结果,如果没问题,修改Github Action中的yml文件,并将相关的环境变量注入
完整github action配置代码:
name: Docker Image CI
on:
push: # push 时触发ci
branches: [main] # 作用于main分支
# pull_request:
# branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 拉取main分支代码
- name: Checkout
uses: actions/checkout@v4.1.7
# 制作docker镜像并推送到阿里云容器镜像服务
- name: build and push docker image
run: |
echo ${{ secrets.ALIYUN_DOCKER_PASSWORD }} | docker login ${{ vars.REGISTRY_MIRROR }} --username ${{ secrets.ALIYUN_DOCKER_USERNAME }} --password-stdin
docker image build -t ${{ vars.APP_NAME }}:latest \
--build-arg NOTION_TOKEN=${{ secrets.NOTION_TOKEN }} \
--build-arg NOTION_ABOUT_DATABASE_ID=${{ secrets.NOTION_ABOUT_DATABASE_ID }} \
--build-arg NOTION_PROJECT_DATABASE_ID=${{ secrets.NOTION_PROJECT_DATABASE_ID }} \
--build-arg NOTION_EXPERIENCE_DATABASE_ID=${{ secrets.NOTION_EXPERIENCE_DATABASE_ID }} \
.
docker tag ${{ vars.APP_NAME }} ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
docker push ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
docker logout
echo "app name ${{ vars.REGISTRY_MIRROR }}"
# 登录远程服务器,拉取镜像,制作并重启容器
# <https://github.com/marketplace/actions/remote-ssh-commands>
- name: ssh remote deploy
uses: fifsky/ssh-action@master
with:
command: |
cd /
echo -e "1.docker login start==>"
echo ${{ secrets.ALIYUN_DOCKER_PASSWORD }} | docker login ${{ vars.REGISTRY_MIRROR }} --username ${{ secrets.ALIYUN_DOCKER_USERNAME }} --password-stdin
echo -e "2.docker stop myblog container==>"
docker container stop ${{ vars.APP_NAME }}
echo -e "3.docker conatainer rm==>"
docker container rm ${{ vars.APP_NAME }}
echo -e "4.docker image rm==>"
docker image rm ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
echo -e "5.docker pull==>"
docker pull ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
echo -e "6.docker container create and start==>"
docker container run -d -p 3000:3000 --name ${{ vars.APP_NAME }} ${{ vars.REGISTRY_MIRROR }}/${{ vars.REGISTRY_NAMESPACE }}/${{ vars.APP_NAME }}:latest
echo -e "7.docker logout==>"
docker logout
host: ${{ secrets.HOST }}
user: ${{ secrets.USER }}
pass: ${{ secrets.PASSWORD }}
- 至此,Next JS项目的生产环境的环境变量算是真正注入成功,只需要等自动化部署成功验证接口请求是否正常
当前页面会通过接口获取Notion文档中的数据: