docker 安装部署 nginx minio nextjs 及 Github Action 实现 CI CD 自动部署

最近使用 Nextjs 写了个博客网站,顺带接触了一下 Docker,Docker 确实真香。目前网站已正常运行,所以文章内的绝大部分应该是不存在大问题的。

本文篇幅应该略长,主要内容如下:

  • 所有容器使用 docker-compose 配置,拒绝过长命令
  • 多 Docker 容器(Nginx 和其它容器)之间的网络桥接
  • 使用 https + 域名
  • Nextjs 开发过程中的部分踩坑(包含 Github OAuth 的踩坑)
  • Github Action 实现 CI CD 构建镜像并重启容器等。

本文中出现的域名统一使用 example.com 作为示例,服务器为 CentOS 7,因为 Docker 命令在不同系统版本中可能不太一样,所以在此说明一下本人的操作环境。

服务器安装 Docker 环境及简单命令介绍

参考地址: https://docs.docker.com/engine/install/centos/

  1. 进入终端,执行如下命令删除系统中的旧版本 docker:
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
  1. 设置 docker 镜像源(阿里镜像源)
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  1. 安装 docker 引擎
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

插件的具体功能这里就不废话了,可以直接去问 AI。

输入两次 y 确认即可等待安装成功

Docker 启动配置

  1. 启动 docker
sudo systemctl start docker
  1. 检查 docker 是否启动成功,随便运行一个 docker 命令
# 检查 docker 启动的进程
docker ps
  1. 设置 docker 启动自启
sudo systemctl enable docker
  1. 设置加速(默认 docker 下载镜像是从 docker hub 下载,设置镜像源)
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://hub.rat.dev",
    "https://docker.wanpeng.top",
    "https://doublezonline.cloud",
    "https://docker.mrxn.net",
    "https://lynn520.xyz",
    "https://ginger20240704.asia",
    "https://docker.wget.at",
    "https://dislabaiot.xyz",
    "https://dockerpull.com",
    "https://docker.fxxk.dedyn.io",
    "https://dhub.kubesre.xyz",
    "https://atomhub.openatom.cn",
    "https://docker.m.daocloud.io",
    "https://docker.udayun.com",
    "https://docker.211678.top",
    "https://docker.nju.edu.cn",
    "https://mirror.iscas.ac.cn",
    "https://docker.xuanyuan.me",
    "https://docker.1ms.run"
  ]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

镜像操作命令(下载镜像, 熟悉命令的可跳过)

  • docker search <name> : 搜索(docker hub)镜像
  • docker pull <name> : 拉取(docker hub)镜像
  • docker images : 查看已下载镜像
  • docker rmi <name> : 删除已有镜像

以 nginx 为例

docker search nginx
docker pull nginx
docker images
docker rmi nginx

容器操作命令(启动容器, 熟悉命令的可跳过)

  • docker run : 运行镜像
  • docker ps : 查看运行中的容器
  • docker ps -a : 查看所有容器(包括已停止的)
  • docker stop : 停止容器
  • docker start : 启动容器
  • docker restart : 重启容器
  • docker status : 查看容器状态
  • docker logs : 查看容器日志
  • docker exec: 进入容器
  • docker rm : 删除容器
  • dokcer rmi : 删除镜像

如果不理解镜像和容器是什么,就以前端的角度来说,镜像就是你的 npm 包,容器就是你的本地项目

Minio 环境搭建

服务器配置较低的不建议使用 minio 在服务器上做资源存储

  • docker pull minio/minio
  • 新建 /data/container/minio 目录,并在 minio 目录下新建 config(配置文件映射) db(文件存储映射) 和 docker-compose.yml 文件
name: minio
services:
  minio:
    image: minio/minio
    container_name: minio
    restart: always
    environment:
      - TZ=Asia/Shanghai
      - MINIO_ROOT_USER=设置登陆账号
      - MINIO_ROOT_PASSWORD=设置登陆密码
    ports:
      - 9000:9000
      - 9999:9999
    volumes:
      - ./db:/data
      - ./config:/root/.minio
    command: minio server /data --console-address ":9999"
  • 启动容器 docker compose up -d
  • 可以运行 docker compose logs -f 查看运行日志

此时可以访问 http://ip:9999,前提是安全组和防火墙放行 9999 端口

Nginx

  • docker pull nginx ,如果需要更小化的可以使用 nginx:alpine 版本
  • nginx:latest 大小约 200M,nginx:alpine 大小约 50M

新建 /data/container/nginx 文件夹,并在 nginx 目录下新建 docker-compose.yml 文件,写入以下内容

services:
  nginx:
    image: nginx # 或 nginx:alpine
    container_name: nginx # 容器名为 nginx
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./html:/usr/share/nginx/html
      - ./logs:/var/log/nginx
      # - ./nginx.conf:/etc/nginx/nginx.conf  # dcoker 不支持文件挂载
      - ./conf.d:/etc/nginx/conf.d # conf.d 文件夹下的 *.conf 会被识别成 nginx 配置文件,便于做不同项目的 nginx 配置区分
      - ./default.d:/etc/nginx/default.d
    environment:
      - TZ=Asia/Shanghai
    privileged: true # 解决nginx的文件调用的权限问题

dcoker 只支持挂载文件夹,所以 /etc/nginx/nginx.conf 无法映射本地目录,会报错,需要提前拷贝出来

docker cp 容器id或名称:/etc/nginx/nginx.conf ./nginx.conf

新建 test.conf 配置文件

docker 默认安装的 nginx 是没有配置 80 服务的,所以我们需要去本机的 conf.d 文件夹(映射的就是容器 /etc/nginx/conf.d)下新建 test.conf 文件,这个 test 随便什么名字都行,写入以下内容,只要是 .conf 结尾能让 nginx 识别到就行。

server {
    listen 80;
    server_name localhost;

    location / {
        # 注意,这里必须是容器的目录地址,而非宿主机的本地目录,就算做了映射,也一定是容器的目录地址
        root /usr/share/nginx/html;
        index index.html;
    }
}

由于我们将本地的 ./html 和容器的 /usr/share/nginx/html 做映射,所以直接在 html 文件夹下新建 index.html,写入点东西,保存,docker restart [容器id或名字],访问 ip 地址即可看到 html 内容。

acme 泛域名证书

看这一部分就默认有域名了,如果没域名,我不确定 acme 能否正常使用,建议先去 acme 仓库 看看

  • docker pull neilpang/acme.sh
  • cd /data/container && mkdir acme

新建 docker-compose.yml,去阿里云 申请一个子账户,添加权限,勾选权限策略 AliyunDNSFullAccess - 管理云解析(DNS)的权限,会生成 idSecret,一定要保存好,仅有一次查看机会

services:
  acme-sh:
    image: neilpang/acme.sh
    container_name: acme
    restart: always
    command: daemon
    environment:
      - Ali_Key="你的 AccessKeyId"
      - Ali_Secret="你的 AccessKeySecret"
    volumes:
      - ./acme:/acme.sh
    network_mode: host
  • 注册邮箱(感觉没什么用): docker exec acme --register-account -m ccc@xx.xxx
  • 注册证书: docker exec acme --issue --dns dns_ali -d example.com -d *.example.com
    • 如果网络执行较慢,第二步可以添加 --server https://acme-v02.api.letsencrypt.org/directory

执行成功后,会显示证书的路径,如下所示

[Sun Jan 26 06:27:52 UTC 2025] Your cert is in: /acme.sh/example.com_ecc/example.com.cer
[Sun Jan 26 06:27:52 UTC 2025] Your cert key is in: /acme.sh/example.com_ecc/example.com.key
[Sun Jan 26 06:27:52 UTC 2025] The intermediate CA cert is in: /acme.sh/example.com_ecc/ca.cer
[Sun Jan 26 06:27:52 UTC 2025] And the full-chain cert is in: /acme.sh/example.com_ecc/fullchain.cer

如果没成功,建议删除镜像重新拉取执行命令

注意事项

不同的运营商的 DNS API 不一样,这里使用 阿里云,则是 dns_ali,如果是其它运营商,请参考 How to use DNS API 来配置 key

  • *.example.com 这里的 * 就代表泛域名

执行完毕命令之后,就可以看到在 acme 目录下,将容器的 acme.sh 文件内容全都映射出来了

定时任务更新证书

安装自动更新脚本

docker exec acme --upgrade --auto-upgrade

在 docker 宿主机添加一条定时任务,让 acme 去检查证书是否过期,即将过期是就会执行脚本自动更新

crontab -e

# 添加以下定时任务
10 0 * * * docker exec acme --cron >> /data/container/acme/acme_cron.log 2>&1
10 1 * * * docker restart [nginx容器名称]

每天零点十分,会检查 acme 证书是否快要过期,若符合,则会自动更新证书,并在 一点十分 重启 nginx 容器

然后 :wq 保存退出

使用 crontab -l 查看定时任务是否添加成功

修改 nginx

在 nginx 的 docker-compose.yml 中添加如下内容,即将 acme 的证书给 nginx 识别,可以使用 https 访问

services:
  nginx:
    volumes:
      - /data/container/acme/acme:/etc/nginx/ssl

然后停止 nginx 容器,重新启动,注意,由于更改是 docker compose,所以必须停止 nginx 容器,然后再使用 docker compose up -d 启动

修改 nginx 配置文件

还是之前的 test.conf 配置文件,将之前最简单的配置删掉,添加以下配置,当访问 80 时重定向到 443 端口

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
   listen 443 ssl;
   http2 on;
   server_name example.com www.example.com;
  #  root         /usr/share/nginx/html;
   ssl_certificate "/etc/nginx/ssl/example.com_ecc/example.com.cer";
   ssl_certificate_key "/etc/nginx/ssl/example.com_ecc/example.com.key";
   ssl_session_cache shared:SSL:1m;
   ssl_session_timeout  10m;
   ssl_ciphers HIGH:!aNULL:!MD5;
   ssl_prefer_server_ciphers on;
   location / {
       root /usr/share/nginx/html;
       try_files $uri $uri/ /index.html;
       index  index.html index.htm;
   }
}

注意: ssl_certificate 这里指定的是 example.com.cer,指向此证书能满足大部分场景的基本使用,但是它不是一个完整的证书链。比如第三方网站访问此域名,可能会因为证书链不完整而出错(我这里遇到 netfily 上访问图片资源一直 502),此时必须指向 fullchain.cer,这是一个完整证书链

重启 nginx 容器:docker restart [容器名称或id]

此时访问 example.com 应该就可以了,如果还是报不安全,建议去运营商的 ssl 管理后台去上传证书,这里以阿里云为例

  • 证书文件 - example.com.cer
  • 证书私钥 - example.com.key
  • 证书链 - ca.cer

这些文件都是 acme 容器映射到主机的本地目录的,都能找到。

nginx 和 minio 网络桥接

现在 minio 的容器和 nginx 的容器是互不干涉的,但是我有(泛)域名之后,想让 nginx 把这个域名代理到 minio。所以需要将两个容器的网络桥接起来,让两个容器共享网络

创建自定义网络桥接,个人不推荐让容器自动创建网络桥接,会导致管理混乱。

docker network create nginx-network

查看现有的网络桥接列表

docker network ls

删除 docker network rm xxx

修改 minio 的 docker-compose.yml

name: minio
services:
  minio:
    networks:
      - nginx-network
networks:
  nginx-network:
    external: true

nginx 的 docker-compose.yml

services:
  nginx:
    networks:
      - nginx-network
networks:
  nginx-network:
    external: true

修改 nginx 的配置文件 test.conf

minio 的可视化 UI 服务,使用 minio.example.com 访问

server {
    listen 80;
    server_name minio.example.com www.minio.example.com;
    return 301 https://$host$request_uri;
}

server {
   listen 443 ssl;
   http2 on;
   server_name  minio.example.com www.minio.example.com;
   ssl_certificate "/etc/nginx/ssl/example.com_ecc/example.com.cer";
   ssl_certificate_key "/etc/nginx/ssl/example.com_ecc/example.com.key";
   ssl_session_cache shared:SSL:1m;
   ssl_session_timeout  10m;
   ssl_ciphers HIGH:!aNULL:!MD5;
   ssl_prefer_server_ciphers on;
   location / {
        # http://minio 这是目前 minio 容器的名称,9999 是容器 UI 的运行端口
        proxy_pass http://minio:9999;
        proxy_set_header Host $http_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;

        # WebSocket 支持, minio UI 界面需要
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

minio 的 API 服务转发,使用 img.example.com 访问

server {
    listen 80;
    server_name img.example.com www.img.example.com;
    return 301 https://$host$request_uri;
}

server {
   listen 443 ssl;
   http2 on;
   server_name  img.example.com www.img.example.com;
   ssl_certificate "/etc/nginx/ssl/example.com_ecc/example.com.cer";
   ssl_certificate_key "/etc/nginx/ssl/example.com_ecc/example.com.key";
   ssl_session_cache shared:SSL:1m;
   ssl_session_timeout  10m;
   ssl_ciphers HIGH:!aNULL:!MD5;
   ssl_prefer_server_ciphers on;
   # 设置客户端请求体大小限制为50MB,不然图片好像高于 1M 就无法通过代码访问展示
   client_max_body_size 50m;

    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;
    proxy_temp_file_write_size 512k;

     # 设置缓存控制头
     add_header Cache-Control "public, max-age=2592000";
   location / {
        proxy_pass http://minio:9000;
        proxy_set_header Host $http_host;
    }
}

当 minio 和 nginx 共享同一个网络后,在 nginx 做转发时,转发的则是容器名称,因为之前 minio 的 docker-compose 配置写的 container_name: minio,则容器运行成功后,其名称就是 minio

Nextjs

Nextjs 部署就不能直接像单页一样打个 dist 包扔服务器上了,整体流程就是根据像代码先构建镜像,就类似于 docker pull 拉取镜像一样,创建一个镜像,然后运行容器

新建一个 Nextjs 项目

Docker 部署 Nextjs 项目

Next 项目根目录添加 .dockerignore

Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git

编写 Dockerfile 构建镜像

使用 pnpm 来管理项目,因此 Dockerfile 和 Nextjs 官方示例并不一样,来源为 掘金,个人感觉此示例还是比较好的,构建出的镜像大约 200M

# 指定基础镜像版本,确保每次构建都是幂等的
FROM node:20-alpine AS base

FROM base AS builder

# 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

# Node v16.13 开始支持 corepack 用于管理第三方包管理器
# 锁定包管理器版本,确保 CI 每次构建都是幂等的
# RUN corepack enable && corepack prepare pnpm@latest --activate
# 这里指定了 pnpm 和本地开发的 pnpm 版本一致,防止出现跨版本的 break change
RUN corepack enable && corepack prepare pnpm@9.15.3 --activate

WORKDIR /app

# pnpm fetch does require only lockfile
# 注意还需要复制 `.npmrc`,因为里面可能包含 npm registry 等配置,下载依赖需要用到
# !!! 不存在的文件不要写入 COPY 命令中,不然镜像会构建失败
COPY package.json pnpm-lock.yaml ./

# 推荐使用 pnpm fetch 命令下载依赖到 virtual store,专为 docker 构建优化
# 参考:https://pnpm.io/cli/fetch
RUN pnpm fetch

# 将本地文件复制到构建上下文
COPY . .

# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1

# 基于 virtual store 生成 node_modules && 打包构建
# 此处不需要与 package registry 进行通信,因此依赖安装速度极快
# 注意 PNPM v8.4.0 版本有一个 breaking change
# 当 `node_modules` 存在,运行 `pnpm install` 会出现命令行交互操作,导致 CI 挂掉
# 这里加上 `--force` 参数,关闭命令行交互操作
RUN pnpm install --offline --force && pnpm build

FROM base AS runner

# RUN apk update && apk add --no-cache git
RUN apk add --no-cache curl

# 如果需要是用 TZ 环境变量 实现时区控制,需要安装 tzdata 这个包
# debian 的基础镜像默认情况下已经安装了 tzdata,而 ubuntu 并没有
# RUN apk add --no-cache tzdata

ARG RUNTIME_ENV
ENV RUNTIME_ENV=$RUNTIME_ENV
ENV NODE_ENV production

# Docker 容器不推荐用 root 身份运行
# 这边先建立一个特定的用户和用户组,为它分配必要的权限,使用 USER 切换到这个用户
# 注意,如果不是 root 权限,对于可执行文件,需要修改权限,确保文件可以执行
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 设置时区
# 在使用 Docker 容器时,系统默认的时区就是 UTC 时间(0 时区),和我们实际需要的北京时间相差八个小时
ENV LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

WORKDIR /app

# PNPM 有一个全局 store,项目中的 node_modules 实际上是全局 store 的 symlink
# 正常需要从上一阶段同时复制 `node_modules` 和全局 store,这样才能正常运行
# 但是由于 `standalone` 目录里面包含所有运行时依赖,且都是独立目录
# 因此可以直接复制该目录,无需复制全局 store(如果复制还会增加镜像体积)
# 另外运行需要的配置文件、dotfile 也都在 `standalone` 目录里面,无需单独复制

# `standalone` 模式打包,默认包含服务端代码,没有客户端代码
# 因为官方建议通过 CDN 托管,但也可以手动复制 `public`、`.next/static` 目录
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# 注意,`standalone` 目录下已经包含了服务端代码,无需再复制 `.next/server`
# COPY --from=builder /app/.next/server ./.next/server

USER nextjs

# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT 3000

# 默认暴露 80 端口
EXPOSE 3000

# 用 standalone 模式打包后,生成的 `standalone/node_modules` 目录下缺少 `.bin` 目录
# 导致无法用 `next` 命令启动项目,但可以用 `node server.js` 启动
# 参考:https://nextjs.org/docs/advanced-features/output-file-tracing
CMD ["node", "server.js"]

Dockerfile 注意事项: 执行 COPY 命令时,COPY 的文件必须存在,否则 COPY 失败会导致镜像构建失败

使用 docker build -t nextjs . 来构建镜像,其中 nextjs 就是镜像名,名字随便起只要能和项目的 docker-compose 使用的镜像名称保持一致即可。

编写 docker-compose 启动容器

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: nextjs # docker build -t nextjs .
    container_name: blog
    ports:
      - 3000:3000
    networks:
      - nginx-network

networks:
  nginx-network:
    external: true
  • nginx-network 就是自定义的网络桥接,之前手动创建了,由于 Nextjs 项目要通过 Nginx 代理,所以必须处于同一个网络桥段下,才能让各容器共享一个网络
  • image: nextjs 这里指定了刚才构建的镜像名为 nextjs 的镜像,如果不指定,执行 docker compose up -d 时,Nextjs 会自动再依据 Dockerfile 重新构建一个镜像
  • container_name: blog 指定容器别名,nginx 代理时可以直接 http://blog:3000 即可代理到该容器的运行地址

配置 Nginx 代理

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
   listen 443 ssl;
   http2 on;
   server_name  example.com www.example.com;
#    root         /usr/share/nginx/html;
   ssl_certificate "/etc/nginx/ssl/example.com_ecc/example.com.cer";
   ssl_certificate_key "/etc/nginx/ssl/example.com_ecc/example.com.key";
   ssl_session_cache shared:SSL:1m;
   ssl_session_timeout  10m;
   ssl_ciphers HIGH:!aNULL:!MD5;
   ssl_prefer_server_ciphers on;
   location / {
     # 注释的是普通 dist 项目所需配置
     #  root /usr/share/nginx/html;
     #  try_files $uri $uri/ /index.html;
     #  index  index.html index.htm;
        proxy_pass http://blog:3000;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   }
}

配置完之后,直接重启 nginx 容器即可。

Github Action 实现 CI CD 自动化部署

每次提交完代码,都去服务器上执行一堆命令,尤其是 Nextjs 项目的镜像构建,挺慢的,所以我们可以把流程自动化,但是我的服务器太垃圾了,跑 Jenkins 的话这服务器基本啥也干不了,所以利用 Github Action 这种取巧的方式来实现自动打包部署。

GitHub 需要的配置

使用 ssh 连接服务器,需要 ssh 私钥,但是私钥不能直接写入配置文件,GitHub 提供了相关功能,可以理解为 env

点击项目仓库的 Settings -> Secrets and variables -> Actions -> new repository secret,填入 key 和 value,多个 secret 需要多次创建,在 GitHub Action 中访问时以 object 对象格式访问。跟 js 的对象语法类似。key 就是对象 key,value 就是 对应的值。

secret key 设置

比如 new repository secret 时添加了 key SSH_PRIVATE_KEY,那么在 GitHub Action 中访问时,就使用 ${{ secrets.SSH_PRIVATE_KEY }}

生成 SSH

ssh 公私钥在 ~/.ssh 下,可以 cd 到该目录下 ls 查看。

如果只有一个 authorized_keys,则需要使用 ssh-keygen 生成公私钥对。具体步骤如下

  • ssh-keygen -t rsa -C "github action" 生成公私钥对
    • 其中 -C "github action" 类似备注,可以省略
  • 生成成功后 ls 查看,此时应该有三个文件 id_rsaid_rsa.pubauthorized_keys
  • cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys,设置公钥内容为 authorized_keys 文件的内容,
  • cat ~/.ssh/id_rsa 将私钥内容添加到 GitHub 仓库的 Secret 中。

编写 Github Action

在项目根目录新建 .github/workflows/deploy.yml 工作流文件

指定 main 分支发生 commit 变动时,会触发 workflow(提前在服务器上拉取一次代码)

name: Deploy Blog

on:
  push:
    branches: main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Connect server to deploy blog
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          command_timeout: 20m
          script: |
            cd ${{ secrets.PATH_REMOTE }}
            MAX_RETRIES=8
            RETRY_COUNT=0

            while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
              echo "▶️ Attempt $((RETRY_COUNT+1))/$MAX_RETRIES: Pulling code..."
              if git pull origin main; then
                echo "✅ Git pull succeeded"
                docker stop blog || true
                docker rm blog || true
                docker rmi nextjs || true
                docker build -t nextjs .
                docker compose up -d
                exit 0
              else
                echo "❌ Git pull failed (attempt $((RETRY_COUNT+1))/$MAX_RETRIES)"
                ((RETRY_COUNT++))
                sleep 5
              fi
            done

            echo "🛑 Error: Failed to pull code after $MAX_RETRIES attempts"
            exit 1

使用 appleboy/ssh-action 连接远程服务器,连接成功后,执行脚本,即 script 部分。

command_timeout 修改为了 20 分钟,默认是 10 分钟,因为有 git pull 的错误重试机制,很容易超过 10 分钟,纯 Docker 构建都要 5 分钟左右

这部分内容其实很少,简单来说就是以下步骤:

  • 拉取 GitHub 仓库的最新代码
  • 停止容器,删除容器
  • 停止镜像,删除镜像

为什么写了一大堆呢?其实就是 git pull 拉取代码失败后的重试机制,因为众所周知的问题,拉取 github 的代码经常失败(当时傻Ⅹ买了台国内服务器,结果自讨苦吃)

Nextjs 开发及部署的简单踩坑

本地 http 访问 https 图片

http 没法访问 https,目前这里有两种方法

  • 在项目的 env 中设置 NODE_TLS_REJECT_UNAUTHORIZED=0 绕过证书问题
  • 使用 next cli,next dev --experimental-https,这个命令在启动项目时生成一份证书临时使用

使用 next-auth 生成环境第三方授权出现 Server Error

环境变量设置 AUTH_TRUST_HOST=true,具体可查看 next-auth issues

容器部署的 Next 项目 next-auth 无法正确推断 redirect_uri

虽然 next-auth 官方说可以自动推断 redirect_uri,本地确实没问题,但是 Docker 部署后的 Next 项目推断失败(笑死)

image

出现问题的时候,推断出来的 redirect_urihttps//: 容器id:容器端口,但是这种显然是无法公网访问的,而且和 Github 里配置的路径不一致,所以会出错,那就没办法了,就去 env 里明确一下呗

  • .env.developmentAUTH_URL=http://localhost:3000/api/auth
  • .env.productionAUTH_URL=https://example.com/api/auth
posted @   风希落  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
历史上的今天:
2024-02-26 Taro 介绍及快速上手
点击右上角即可分享
微信分享提示