Life and freedom Ge Lin ——— Draw by Razzh

Docker 部署 Twist Icons 文档

Feb 17 · 12 min

本文开始之前,容我介绍一下这篇文章的背景。

本文记录了作者部署 Twist Icons 文档 的过程。

首先 Twist Icons,我由本人开发的 Icons 库,打包了许多知名图标库的图标,具体介绍可以观看这篇文章:传送门

这次打包的项目是基于 docker 打包的,所以希望你拥有 docker 相关的基础知识,并按照官方提供的项目示例配合 docker 打包我们的项目。

整体思路是把打包 NextJS 的代码并在 docker 里面跑起来,并配置 Nginx 反向代理到 docker 容器中启动的地址上。

#开始操作!

#添加 Nextjs 配置

文档中让我们先配置 next.config.js 中的 output 字段,将其设置成 standalone,设置成这样的目的是什么?

我在官方文档找到了说明:Next.js can automatically create a standalone folder that copies only the necessary files for a production deployment including select files in node_modules.

总结一下就是帮我们减少打包体积的,我们添加上就好。

#添加 Dockerfile

# 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 2010
 
ENV PORT=2010
 
# 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"]

我直接使用的 Dockerfile 文件,只对Node版本和端口进行了修改,我指定了Node版本为20,端口号默认是3000,你可以修改成其他的,我这里是修改成了 2010,但要记得我们需要在 package.json 中同步修改 start 命令:

"start": "next start -p 2010",

#本地制作镜像

我们利用 Dockerfile 文件制作 docker 镜像,我使用的环境是 MacOS M1

docker buildx build --platform=linux/amd64 -t twist-icons --no-cache .

这里指定了镜像对应的平台 linux/amd64,打包出来的镜像兼容性好,可以运行在 linux/amd64/v8linux/amd64/v4 的机器上。

需要稍等几分钟,因为 Twist Icons 收集了非常多的本地图标,采用的是 NextJS静态(Static)打包方案,所以生成的静态HTML会非常大,打包比较耗时且占用机器资源。

这里为什么不在服务器上构建镜像?
我尝试过在服务器中直接构建Dokcer镜像,结果是失败的。因为在构建 Twist Icons Docs 的镜像占用的大量的内存,我的服务器是2核2GB的,上文说过打包这样的项目会占用大量的内存资源,故打包镜像失败了,所以采用运用本地机器的算力来打包镜像。

如果服务器配置允许,可以直接在服务器上配置 git 拉取最新代码在云上打包 docker 镜像运行。

#保存镜像

因为本地镜像不支持直接上传到服务器,所以我们打包成 tar 格式再上传到服务器进行解压。

docker save -o twist-icons.tar twist-icons:latest

#加载镜像

我们通过 scp 工具上传文件到服务器上,我这里是上传到了 /home 目录下。

然后我们就可以运用 docker load 来加载镜像了。

docker save -o twist-icons.tar twist-icons:latest

#运行镜像

端口号 A:B,其中端口号A是外部访问服务器的端口号,你需要在阿里云的服务器安全组开放这个端口,我这里是设置了2010。

端口号 B,是之前运行 Dockerfile 文件暴露(EXPOSE)的端口号,需要与其一致。

docker run -d --rm -p 2010:2010 twist-icons:latest

#Nginx 配置

这里一样是运行 dockernginx上文我们部署了博客在 Nginx 中。

docker container run \
  --rm \
  --name mynginx_1 \
  --volume "$PWD/html":/usr/share/nginx/html \
  --volume "$PWD/conf":/etc/nginx \
  --label=sh.acme.autoload.domain=razzh.cn \
  --add-host=host.docker.internal:host-gateway \
  -p 80:80 \
  -p 443:443 \
  -d \
  nginx

这里的启动项配置较上文的启动配置新增 --add-host 选项,原因是我尝试过在 conf 的反响代理选项 proxy_pass 的时候将 icons.razzh.cn 代理到 http:127.0.0.1:2010 这个地址上,但是访问时服务器却响应的是502报错。

通过使用 docker logs 命令,我们打印容器的错误日志:

docker logs -f <container_id>

跟着错误提示,顺藤摸瓜在这个 Stackflow 下找到了答案:

image

单单修改配置是不能解决问题的

This won't work automatically, but you need to provide the following run flag

底下的老哥评论补充道:我们还需要在运行镜像的时候添加这个配置,所以我们添加上了这个配置,问题解决了~

所以完整的 Nginx 配置如下:

#/etc/conf/conf.d/icons.razzh.cn.conf 
server {
    listen       80;
    server_name  icons.razzh.cn;
 
    #80跳转到443
    rewrite ^(.*)$ https://${server_name}$1 permanent;
 
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
 
server {
    listen 443 ssl http2;
    server_name  icons.razzh.cn;
 
    ssl_certificate          /etc/nginx/certs/razzh.cn/full.pem;
    ssl_certificate_key      /etc/nginx/certs/razzh.cn/key.pem;
 
    ssl_session_timeout  5m;
 
    #开启HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
 
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    #适时移除TLSv1.2
    ssl_protocols TLSv1.3 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
 
    location / {
        root /usr/share/nginx/html;
        proxy_pass http://host.docker.internal:2010;
        index index.html index.htm;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
 
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}

配置完成后我们重新启动一下 Nginx 容器,我们的配置就生效了。

docker restart container name

#docker 无法拉取镜像

最后补充一下踩的坑吧,如果想在本地制作一个 docker 镜像,docker hub 在国内时常访问不了,打包过程抛出类似 authtoken 不可访问的问题,我们可以在 docker desktop 中的设置 镜像源

具体路径:Settings -> Docker Engine 中添加镜像源,截止在发文前这个镜像源是可以使用的,但是可能也会失效,到时候需要自己 google 一下可用的镜像源更新一下就行了。

{
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "enabled": true
    }
  },
  "experimental": false,
  "features": {
    "buildkit": true
  },
  "registry-mirrors": [
    "https://docker.1ms.run"
  ]
}

如果是想配置服务器上的 docker 镜像,可以直接修改 /etc/docker/daemon.json 文件,添加 registry-mirrors 字段就可以了。

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://rh65k0v7.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

阿里云也提供了 dokcer 镜像的加速服务,但是这个也不稳定,前几天可以拉取镜像,现在拉取总是提示 timeout (不靠谱)😅,所以需要自己找可用的源。

浙ICP备2024129591号-1
春秋(Live) - 张敬轩
--:-- / --:--
  1. 1春秋(Live)张敬轩
  2. 2不吐不快(live)张敬轩
  3. 3男孩最痛(live)张敬轩
  4. 4粤语残片(live)陈奕迅
  5. 5几分之几(live)卢广仲
  6. 6地球很危险古巨基
  7. 7樱花树下(live)张敬轩

春秋 (Live) - 张敬轩 (Hins Cheung)

词:林夕

曲:Edmond Tsang

那夜谁将酒喝掉

因此我讲得多了

然后你摇着我手拒绝我

动人像友情深了

我没权终止见面

只因你友善依然

仍用接近甜蜜那种字眼通电

没人应该 怨地怨天

得到这结局

难道怪罪神没有更伪善的祝福

我没有为你伤春悲秋不配有憾事

你没有共我踏过万里

不够剧情延续故事

头发未染霜 着凉亦错在我幼稚

应快活像个天使

有没有运气再扮弱者 玩失意

有没有道理为你落发

必须得到世人同意

心灰得极可耻 心伤得无新意

那一线眼泪 欠大志

爱若能堪称伟大 再难挨照样开怀

如令你发现为你而活到失败

令人不安 我品性坏

我没有为你伤春悲秋不配有憾事

你没有共我踏过万里

不够剧情延续故事

头发未染霜 着凉亦错在我幼稚

应快活像个天使

有没有运气再扮弱者玩失意

有没有道理为你落发

必须得到世人同意

心灰得极可耻 心伤得无新意

那一线眼泪 欠大志 太没意思

若自觉这叫痛苦未免过份容易

我没有被你改写一生怎配有心事

我没有被你害过恨过

写成情史 变废纸

春秋只转载要事

如果爱你欠意义

这眼泪 无从安置

我没有运气放大自私的失意

更没有道理在这日

你得到真爱制造恨意

想心酸 还可以 想心底 留根刺

至少要见面上万次