· 8 min read

使用 Github Actions 为其他项目构建 Docker Image

使用 Github Actions,为自己喜爱的 Github 开源项目,快速、独立、自动化地构建 Docker 镜像,并推送到 ghcr (GitHub Container Registry)。

使用 Github Actions,为自己喜爱的 Github 开源项目,快速、独立、自动化地构建 Docker 镜像,并推送到 ghcr (GitHub Container Registry)。

背景

这两天搞起了自动追番,使用了 AutoBangumi 及其部署文档使用的方案组了个套件。感觉还不错,使用 Plex 播放,也让我的 iPad 终于能好好当个播放器了。

其中就包括一个 qBittorrent 程序,自带的 Web UI 很工具化,不是很漂亮,也不是很好用。所以我就找了第三方的,正好它没有单独的 Docker image,所以我就做个自动化构建吧。

本来想在自建的 Gitea Actions 运行的,不过想了想,这个丢 Github 上跑比较合适,反正源头都在 Github 上。之前也有注意到做同样事情的库,但是一直想不起来是什么项目,就没找到……那作业没得抄,只能自己写了。

对了,我选择的 qBittorrent Web UI 是 VueTorrent

方案

方案很简单,将目标项目作为 Git Submodule 放在我们的构建 Repo 中。然后分为两步:

  1. 使用定时任务,每天检查最新发布的版本。将最新版本拉到项目中并提交。
  2. 使用 push 触发器,将新版本构建成 Docker image 并推送到 ghcr (GitHub Container Registry)。

项目地址在这:IvanLi-CN/vue-torrent-docker: Automatically build VueTorrent Docker images

实施

下面就是流水帐了。实施这个方案的话,我是先做第二步。因为我当时需要一个 Docker image 来替换原始方案。在文章里,为了流程顺畅,就按上帝视角,用正常的顺序来编写吧。毕竟不是教程,就不循序渐进了。

定时检查上游更新

Github Actions 支持使用 Cron 来创建一个定时任务。 所以触发 Action 的问题轻松解决。

on:
  schedule:
    - cron: '0 2 * * *'

那么第二步就是获取上游最新的发布版本了。 上游使用 “Github Releases” 发布版本:

Github Releases for VueTorrent

所以这里使用了 git-get-release-action,获取最新的版本号和 commitish hash。 其中版本号就是 tag name,而 commit-ish hash 就是平常使用的 commit hash 了。

- name: git-get-release-action
  id: git-get-release
  uses: cardinalby/git-get-release-action@1.2.4
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    repo: WDaan/VueTorrent
    latest: true

这里提供了几个参数:

  • GITHUB_TOKEN 是自动注入的,我们将这个变量作为环境变量提供给 git-get-release-action 就行,缺少这个变量,这个步骤会报错。
  • 我们通过 repo 提供了上游的仓库名称 “WDaan/VueTorrent”,这个仓库在 Github 上,所以可以直接这么简写。
  • 因为只需要获取最新的发布版本,所以提供了 latesttrue

这一步骤会有两个关键的输出:

  • tag_name 是我们想要的最新版本的版本号,因为他的 git tag 写的就是版本号,例如现在的 v1.6.0
  • current_commitish 就顾名思义了,接下来就是要用他作为当前 commit 的唯一标记。

我们有了最新版本的 commit-ish 值,那就要有上次构建 Docker image 时用的 commit-ish 值。

- name: Get current commitish
  id: get-current-commitish
  run: |
    cd vue-torrent
    echo "Current commitish: $(git rev-parse HEAD)"
    echo "{current_commitish}=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT

这里直接进入子模块的目录里,使用 git rev-parse HEAD 命令获取当前 repo 使用的上游的 commit-ish。 因为我们每次构建镜像前,都会先更新子模块的 commit 位置,所以这里就是上次发布镜像时的 commit-ish 了。

再因为我们一直是单调地往按时间往更新的构建,所以不需要比较 commit 的新旧,只要不一样,就是要构建新的镜像。 所以我们这一步,就直接比较最新的 commit 和现在的 commit 是否一直:

- name: Compare versions
  id: compare
  run: |
  echo "Current version: ${{ steps.git-get-release.outputs.tag_name }}"
  echo "{should_update}=${{ steps.git-get-release.outputs.target_commitish != steps.get-current-commitish.outputs.current_commitish }}" >> $GITHUB_OUTPUT

下面就是重要的三个步骤了,一更新子模块,二提交更新,三打标签。

更新子模块

- name: Update
  if: steps.compare.outputs.should_update == 'true'
  working-directory: ./vue-torrent
  run: |
    git fetch --depth=1 origin ${{ steps.git-get-release.outputs.tag_name }}
    git checkout -b ${{ steps.git-get-release.outputs.tag_name }} ${{ steps.git-get-release.outputs.target_commitish }}
    git reset --hard HEAD

这里使用了 working-directory 更改执行目录到子模块中,用 cd 进去应该也一样。

因为之前检出存储库时是只检出最新的那个 commit,所以需使用 git fetch 将我们需要的那个 commit 拉到运行环境中,否则会报形如 fatal: Could not parse object '6ab00a179b9509ef162a14862fb828c78144caff'. 的错误。

之后使用 git checkout -b 将目标的 commit 拉到新的分支上。

最后,将当前的位置设到 HEAD,即目标 commit。

后两步应该是可以直接改成 git reset --hard ${{ steps.git-get-release.outputs.target_commitish }},不过我没试过,仅供参考。

提交更新

- name: Commit changes
  if: steps.compare.outputs.should_update == 'true'
  run: |
    git diff
    git config user.name "GitHub Actionss"
    git config user.email "bot@noreply.github.com"
    git add .
    git commit -m "Update to ${{ steps.git-get-release.outputs.tag_name }}"
    git push origin ${{ github.ref_name }}

这就没什么好说的了,需要注意的一点就是权限问题。因为我们是 push 到当前的 repo 上,所以可以直接使用自动注入的 GITHUB_TOKEN,不过需要在 repo 的设置页面更改下权限:

Github Actions Permissions Setting

选择 “Read and write permissions”,这样就能写入当前的 repo。

打标签

- name: Tag
  if: steps.compare.outputs.should_update == 'true'
  run: |
    git tag ${{ steps.git-get-release.outputs.tag_name }}
    git push origin ${{ steps.git-get-release.outputs.tag_name }}

目的就是后面构建镜像时,能方便地从这里取到版本号。

构建镜像

这个就比较简单了,代码在这:

Action

Dockerfile

因为直接做成子模块了,流程就简单很多了,只要检出代码时将子模块一并检出,之后直接构建 Docker 镜像就行。

我也不知道有没有人用,只构建了 x86 的自用。

最后

分享一下我现在用的追番的 Docker Compose 吧:

docker-compose.yaml

version: '3.2'
services:
  caddy:
    container_name: caddy
    ports:
      - ${QB_PORT}:80
    networks:
      - auto_bangumi
    restart: unless-stopped
    volumes:
      - ./caddy:/etc/caddy
    image: caddy:2
  vuetorrent:
    container_name: vuetorrent
    expose:
      - 3000
    networks:
      - auto_bangumi
    restart: unless-stopped
    image: ghcr.io/ivanli-cn/vue-torrent:main
  qbittorrent:
    container_name: qBittorrent
    environment:
      - TZ=Asia/Shanghai
      - TemPath=/downloads
      - SavePath=/downloads
      - PGID=${GID}
      - PUID=${UID}
      - WEBUI_PORT=8080
    volumes:
      - qb_config:/config
      - ${DOWNLOAD_PATH}:/downloads
    ports:
      - 6881:6881
      - 6881:6881/udp
    networks:
      - auto_bangumi
    restart: unless-stopped
    image: superng6/qbittorrent:latest

  auto_bangumi:
    container_name: AutoBangumi
    environment:
      - TZ=Asia/Shanghai
      - PGID=${GID}
      - PUID=${UID}
      - AB_DOWNLOADER_HOST=qbittorrent:${QB_PORT}
    networks:
      - auto_bangumi
    volumes:
      - ./auto_bangumi/config:/app/config
      - ./auto_bangumi/data:/app/data
    ports:
      - 7892:7892
    dns:
      - 8.8.8.8
      - 223.5.5.5
    restart: unless-stopped
    image: estrellaxd/auto_bangumi:latest
    depends_on:
      - qbittorrent

  plex:
    container_name: Plex
    environment:
      - TZ=Asia/Shanghai
      - PUID=${UID}
      - PGID=${GID}
      - VERSION=docker
      - PLEX_CLAIM=${PLEX_CLAIM}
    networks:
      - auto_bangumi
    ports:
      - 32400:32400
    volumes:
      - plex_config:/config
      - ${DOWNLOAD_PATH}/Bangumi:/tv
    restart: unless-stopped
    image: lscr.io/linuxserver/plex:latest

networks:
  auto_bangumi:

volumes:
  qb_config:
    external: false
  plex_config:
    external: false

caddy/Caddyfile

:80 {
  reverse_proxy /api/* qbittorrent:8080
  reverse_proxy /* vuetorrent:3000
}

.env

QB_PORT=8080
DOWNLOAD_PATH=/home/ivan/downloads
UID=1000
GID=1000

PLEX_CLAIM=claim-DwbcewEB7j3pmNotG_eT
Back to Blog

Related Posts

View All Posts »
使用 Verdaccio 自建 Node 存储库

使用 Verdaccio 自建 Node 存储库

作为靠着 JavaScript 生态吃饭的 Web 开发者,自建一个 Node regsitry 是很有必要的,我这次继续选择 Verdaccio 来搭建存储库。这次使用 Docker Compose 部署 Verdaccio,并将 Caddy 用于反向代理该服务。

使用 Xray、acme.sh、Docker Compose 搭建内网穿透服务

为了能在外直接访问家中网络,我组建了三套方案,一是 [[Xray]],二是 [[ZeroTier]],三是 [[NPS]]。今天,我准备在我上个月购入的服务器上再部署一套 Xray 服务,提高可用性。本次准备完全仰仗 Docker 容器,让我未来迁移服务更加省事。

2023 年,再组一台黑苹果 ITX 主机

2023 年,再组一台黑苹果 ITX 主机

这是第二次组黑苹果台式机了,上一次是第一次,所以保守地选择了 i5-10400 + B460M + 6600XT 的组合。因为最近感受到 CPU 性能有一些吃紧,并且 ITX 的遗憾又开始涌上心头,最后看到 V 站老哥出 10700K + Z490I。顺势入手了。但是夜长梦多,挑选了半天的配件,最后发现应该买 12600K 比较合适。只能友好地鸽掉了 TAT

React 18,新的严格模式

好好的 useMemo、useEffect 居然执行了两次,我明明传入了依赖,为什么会执行两次呢?原来是 React 18 的破坏性改动!