docker 部署 Gatsby 应用

用 Gatsby 重构自己的博客之后,他的众多优点比如可以用 react 组件化开发,可以享受 ssr、单页应用也能 seo 等等,感觉一直很爽。 但随着 Gatsby 和它的相关插件同步升级,下载、构建失败的事情经常爆出,每一个都是时间杀手,解决一次可能要花掉一个晚上。主要是跟图片相关的几个插件 gatsby-transformer-sharp ,需要同构c构建的模块,国内网络经常下载失败。

gatsby-plugin-sharp,依赖pngquant-bin ,这个包下载也会经常出问题 pngquant-bin@5.0.1 postinstall: node lib/install.js · Issue #97 · imagemin/pngquant-bin · GitHubpngquant-bin@5.0.2 postinstall · Issue #104 · imagemin/pngquant-bin · GitHub gatsby-image,依赖 mozjpg ,mozjpeg 的编译需要 libtool automake autoconf nasm。

最近出现的的构建图片出错 construct-error.js - child “error” fails because “error” must be an object · Issue #15242 · gatsbyjs/gatsby · GitHub

而这个 issue 依然没有 close: gatsby-plugin-sharp: WorkerError @2.6.27 · Issue #26723 · gatsbyjs/gatsby · GitHub ,试了各种 hack 方法没法解决,我只好将版本回滚并锁定。

问题是如果还有其他服务也做了依赖升级导致依赖冲突怎么办?如果以后我重装系统、更换云服务器,岂不是还要重走一遍下载安装构建的坑?万一倒时候又有新坑怎么办? 因此我开始着手把 Gatsby 项目 docker 化,做成镜像传到 docker hub。即便以后换个运行环境也可以快速使用,不用再被时间杀手折磨。

准备工作

制作项目镜像

gatsby 的 docker 镜像有两个 tag 版本:

  1. latest - 会起一个nginx serve 静态文件,需要手动把 public 目录拷过去
  2. onbuild - 构建并生成一个新的docker镜像

这里直接用 onbuild 版本的镜像,新建 dockerfile.dockerignore

Shell Session
touch Dockerfile .dockerignore

dockerfile 中添加:

Shell Session
FROM gatsbyjs/gatsby:onbuild

.dockerignore 中添加:

Shell Session
.cache/
node_modules/
# 不要添加 public/

这里没有参考 gatsby 镜像的文档添加 public/  ,因为接下来会将 gatsby 网站项目打包构建出静态资源,/public 目录即为其静态资源目录:

Shell Session
gatsby build

接下来创建镜像:

Shell Session
docker build -t albertaz.com:latest .

这里用 -t 打上了 latest 标签, albertaz.com 是我的镜像名。由于我在 dockerfile 同级目录创建镜像,所以最后的 . 不能省略。

整个镜像制作时间大概会持续两道三分钟,时间很快,主要原因在于 gatsby 自身的 docker 镜像使用了一个轻型的 linux 系统发行版 alpine ,整个体积极小只有 4.4 M,而我生成的网站项目镜像也只有 13 M。

Shell Session
➜  my-blog docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
albertaz.com        latest              ba541dad4832        3 minutes ago       14.2MB

接下来运行着这个镜像试试看:

Shell Session
docker run --rm -p 80:80 albertaz.com

镜像运行会创建一个 container,--rm 表示在 container 停止后自动清除, -p 80:80 表示把本地机器的 80 端口映射到 container 的 80 端口(也是 container 的默认端口),所以接下来可以通过浏览器打开 localhost 访问网站了。

优化镜像配置

前面提到 gatsby 镜像会在运行时起一个 nginx serve 静态文件,其实是因为镜像里也安装了一个 nginx。镜像文档里还提到了通过两种方式修改 nginx 配置: 一种是修改 Dockerfile, 使用自定义的 nginx 配置文件:

Bash
FROM gatsbyjs/gatsby:latest
COPY nginx-server-rules.conf /etc/nginx/server.conf

另一种是通过 docker run 命令 的 --env-file  传参:

Bash
docker run --rm -e CACHE_IGNORE=html -p 80:80 albertaz.com

但如果我需要加多个参数,配置更多端口、网络、启动策略等等,这个命令会越写越长,显然用一个脚本文件写这些指令会更好。不过当我在 mac 上装 docker desktop 的时候,它自带了 docker-compose,docker-compose 的意义是让我们可以编排多个 容器、不用写复杂的 shell 脚本。现在尽管只有一个容器,但依然可以借助 docker-compose,通过 yaml 配置文件的方式来运行,这个方式显然更有拓展性。

在项目根目录下创建一个 docker-compose.yml 文件:

Bash
version: "3.8"
services:
  albertaz.com:
    image: albertaz.com
    environment:
      - CACHE_IGNORE=html
    ports:
      - "80:80"

可以看到写法更有结构性、更易读。随后就可以通过 docker-compose 运行镜像:

Bash
## 启动
docker-compose up -d

## 停止
docker-compose down

docker-compose 在停止运行时会自动清除 container,不用像 docker run 那样加 —rm 参数。

部署

上传到 docker hub

本地自测没问题之后,开始把创建好的镜像上传到 docker hub,当然前提是需要注册一下账号,拿到一个 docker hub 的用户名。 上传前先把本地镜像通过 docker tag  关联到 docker hub

Bash
docker tag albertaz.com:latest albertaz/albertaz.com:latest

albertaz/albertaz.com:latest 中第一个 albertaz 就是 docker hub 的用户名。 随后上传:

Bash
docker push albertaz/albertaz.com

服务器运行镜像

上传完成后,回到服务器,装好 docker 和 docker-compose。因为我依然想用 docker-compose 运行镜像,所以需要再把 docker-compose.yml 文件也上传到服务器,这个文件之前可不会被 push 到 docker hub。随后启动镜像:

Bash
docker-compose up -d

服务器会从 docker hub 拉取我们配置好的网站镜像,不用再跑一遍安装构建流程了。

更新镜像

另外如果我网站更新了,需要更新镜像文件,利用 docker-compose 做法很简单,直接在启动命令后加上 container 名即可:

Bash
docker-compose up -d albertaz.com

注意更新的时候原来的 container 大概率是在运行的,docker-compose 可以让 container 基于新的镜像运行,无需停止原来正在运行的 container。但如果直接通过 docker 原生命令运行,就需要先 docker container stop 停止 container,重新 docker run ,所以网站更新时会有一段无法访问的时间。这点也是选择用 docker-compose 的重要原因之一。