个人网站的部署变迁史
发表于 阅读时长12分钟
首先是分享自己网站的整体变迁流程
第一阶段
最开始是使用hexo + github pages
第二阶段
nextjs blog template vercel 好处是方便,坏处是要翻墙。
第三阶段
隔了几年,使用cra来搭建react项目,然后改成使用vite,打包后手动上传,纯前端单页应用,再用nginx来起服务。
第四阶段
宝塔
第五阶段
docker容器化
Docker + Nginx
单个nextjs镜像在本地运行起来后,使用nginx代理到3000端口
Docker-Compose + Github actions
这应该是我个人网站的终极答案了,后续也不想继续折腾部署这块了。
推荐一个xshell工具,termius。
ACR_NAMESPACE是阿里云的容器镜像仓库的命名空间 ARC_USERNAME是阿里云的容器镜像仓库的用户名 ARC_PASSWORD是阿里云的容器镜像仓库的密码, ARC_REGISTRY是阿里云的容器镜像仓库的仓库地址,这个在网页上有展示
SERVER_IP是服务器的公网ip地址 SERVER_SSH_KEY是服务器的ssh密钥,这里需要注意,读取的是哪种格式的内容,之前我因为一直用rsa格式的,结果authorize_keys里的不是这种格式。
SSH_PORT ssh端口,默认22 SSH_USERNAME 默认root
后来尝试了一下宝塔
域名的申请、https、阿里云服务上申请免费的证书、nginx配置https证书 备案
之前我使用了一个github上的nextjs博客模板,搭配vercel来跑我的个人博客。
这次我想除了博客外,还应该包括一些而外的东西,比如自己灵光乍现的项目,生活等等。
还是沿用了next,同时我希望在国内的网络环境下也能正常访问,所以我打算使用国内的服务器备案后,自己跑nextjs项目
一开始没做好了解,以为nextjs项目和普通的react项目没有区别,打包后,拖个dist目录到服务器上就完事了。
然而发现不是。
这种服务端渲染的项目和之前的静态页面展示有很大的不同。
我需要在我的服务器上直接把整个项目文件上传上去,然后运行这个项目。相当于在服务器上不打包,直接跑项目。
如果配置了安全组,我能通过ip加服务的端口号3000来直接访问这个项目。
同时为了这个项目能够持续运行,还需要使用pm2来守护这个运行的进程。
之后是nginx的配置,需要修改成代理到localhost:3000里去
在域名备案还未成功时,遇到一些https直接访问无法访问的问题,当时还联系了阿里云的客服,让他们帮我解答疑惑。
之后能顺利访问了。
接下来便是解决最大的问题,每次更新代码后,需要在服务器上git pull最新代码,然后把服务停了重新跑这一系列人工操作问题。
我自然而然想到的是github actions,不过之前我可能会想到jenkins来部署,但是jenkins我个人感觉还是有点繁琐。
所以我选用了github actions + docker来部署我的nextjs项目。
修改next.config.js文件,我的项目里是next.config.mjs,把output设置为standalone,然后打包。
import createMDX from "@next/mdx";
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
output: "standalone",
};
const withMDX = createMDX({
extension: /\.mdx?$/,
});
export default withMDX(nextConfig);
在服务器上安装docker。 docker的一些命令。 比如build打包镜像,run运行容器等。
dockerfile内容,这里是直接参照官网给的示例,唯一的改动是把node的版本从18改成了我服务器上当前的node版本20
# syntax=docker.io/docker/dockerfile:1
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* .npmrc* ./
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
# 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/config/next-config-js/output
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
这里遇到一个问题是,当我使用docker build运行时,直接报错
=> ERROR [internal] load metadata for docker.io/library/node:20-alpin 30.2s
------
> [internal] load metadata for docker.io/library/node:20-alpine:
------
Dockerfile:3
--------------------
1 | # syntax=docker.io/docker/dockerfile:1
2 |
3 | >>> FROM node:20-alpine AS base
4 |
5 | # Install dependencies only when needed
--------------------
ERROR: failed to solve: DeadlineExceeded: DeadlineExceeded: DeadlineExceeded: DeadlineExceeded: failed to resolve source metadata for docker.io/library/node:20-alpine: failed to do request: Head "https://registry-1.docker.io/v2/library/node/manifests/20-alpine": dial tcp 108.160.166.148:443: i/o timeout
因为一开始我配置的docker镜像地址是阿里云提供的,发现根本用不了,只能改daemon.json里的registry-mirrors地址。
docker compose 管理nextjs容器和nginx容器。
services:
nextjs:
# build:
# context: . # 使用当前目录作为构建上下文
# dockerfile: Dockerfile # 指定 Dockerfile 路径
image: ${ACR_FULL_ADDRESS}/website:${IMAGE_TAG}
container_name: nextjs
ports:
- "3000:3000"
restart: always
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80" # 将宿主机的 80 端口映射到 Nginx 容器的 80 端口
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf # 挂载 Nginx 配置文件
- /usr/local/nginx/conf/cert:/etc/nginx/cert
depends_on:
- nextjs
restart: always
首先需要在项目下建dockerfile和nginx.conf。 dockerfile的作用是用于打包一个nextjs项目镜像,然后把nextjs 在npm run build之后的一些文件映射到容器里去。
docker-compose里则是把我们nextjs项目里配置的nginx.conf映射到nginx容器的配置里去,同时把服务器上的https证书也映射进去。
github actions的流程
name: Build, Push, and Deploy
on:
push:
branches: [ main ]
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Login to Aliyun ACR
uses: docker/login-action@v3
with:
registry: ${{ secrets.ACR_REGISTRY }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and Push Docker Images
run: |
export IMAGE_TAG=${{ github.sha }}
export ACR_FULL_ADDRESS=${{ secrets.ACR_REGISTRY }}/${{ secrets.ACR_NAMESPACE }}
# 只构建一次镜像
docker build -t $ACR_FULL_ADDRESS/website:$IMAGE_TAG .
docker push $ACR_FULL_ADDRESS/website:$IMAGE_TAG
deploy:
runs-on: ubuntu-latest
needs: build-push
steps:
- name: SSH to ECS and Deploy
uses: appleboy/ssh-action@v1.2.1
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
debug: true
script: |
# 登录阿里云镜像仓库
echo ${{ secrets.ACR_PASSWORD }} | docker login ${{ secrets.ACR_REGISTRY }} \
-u ${{ secrets.ACR_USERNAME }} \
--password-stdin
# 拉取最新镜像并启动容器
export IMAGE_TAG=${{ github.sha }}
export ACR_FULL_ADDRESS=${{ secrets.ACR_REGISTRY }}/${{ secrets.ACR_NAMESPACE }}
# 拉取新镜像
docker pull $ACR_FULL_ADDRESS/website:$IMAGE_TAG
# 同步仓库信代码
cd /www/wwwroot/zzhwhd.com/zzhwhd
git reset --hard origin/main
git pull origin main
git checkout main
# 停止并删除旧容器
docker compose -f /www/wwwroot/zzhwhd.com/zzhwhd/docker-compose.yml down
# 清理旧镜像,只保留最新的一个
docker images "$ACR_FULL_ADDRESS/website" -q | tail -n +2 | xargs -r docker rmi -f
# 清理悬空镜像
docker image prune -f
# 启动新容器
docker compose -f /www/wwwroot/zzhwhd.com/zzhwhd/docker-compose.yml up -d
阿里云docker镜像仓库 命名空间、项目名称
在代码提交到main分支时触发action
在github上配置secrets
首先是登录我的阿里云docker镜像仓库,把我github上最新代码docker build后推送到这个镜像仓库上。
然后我登录阿里云ecs服务器,拉取最新的镜像,同时删除旧的容器,准备跑我新的容器。
这里我遇到一个问题,就是明明我已经构建了最新的镜像,我看镜像上的tag也是我最新的提交。但是我访问页面时,看到的还是旧的内容。
起初我以为是nginx缓存的问题,给nginx.conf的访问上全都设置了不缓存,结果发现一点用的没有。
知道我在宝塔上,访问文件路径,点开我更改的文件内容时,发现仍旧是旧内容。此时我才发现端倪。
原来之前我的dockerfile一直配置有问题,每次打包都会基于dockerfile所在目录的文件进行重新构建。
即使我拉取了镜像仓库的最新镜像,然而我使用的nextjs镜像还是基于服务器上的旧代码重新构建的镜像。
所以我调整了dockerfile的配置以及deploy.yml的配置,同时把原来nginx的无缓存配置给还原了。
现在在登录到阿里云服务器是,我的deploy流程还有一步,是会去git pull拉取一遍代码,确保是最新的,因为比如我的docker compose运行是基于这个远程目录下的docker-compose.yml,所以我要确保它也是最新的, 免得我有时更新了这些配置,在运行的时候却还是用老配置。
目前我的网站已经完全自主可控啦,撒花!