GitHub Actions 自动构建镜像 并发布到 Docker Hub

引言

  • 通过GitHub的源代码自动构建镜像
  • 将镜像上传到 Docker Hub
  • 自动部署:远程服务器 pull Docker Hub

本文以 SimCaptcha 项目为例。

deploy-docker.yml

.github/workflows/deploy-docker.yml

deploy-docker.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
name: Docker Image CI/CD
on:
push:
branches: [ master ]
jobs:
# 构建并上传 Docker镜像
build:
runs-on: ubuntu-latest # 依赖的环境
steps:
- uses: actions/checkout@v2
- name: Build Image
run: |
docker build -t yiyungent/simcaptcha -f examples/EasyAspNetCoreService/Dockerfile .
docker build -t yiyungent/simcaptcha-client -f examples/AspNetCoreClient/Dockerfile .
- name: Login to Registry
run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}
- name: Push Image
run: |
docker push yiyungent/simcaptcha
docker push yiyungent/simcaptcha-client

# Docker 自动部署
deploy-docker:
needs: [build]
name: Deploy Docker
runs-on: ubuntu-latest
steps:
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }} # 服务器ip
username: ${{ secrets.HOST_USERNAME }} # 服务器登录用户名
password: ${{ secrets.HOST_PASSWORD }} # 服务器登录密码
port: ${{ secrets.HOST_PORT }} # 服务器ssh端口
script: |
# 切换工作区
cd simcaptcha
# 下载 docker-compose.yml
wget -O docker-compose.yml https://raw.githubusercontent.com/yiyungent/SimCaptcha/master/docker-compose.yml
# 停止并删除旧 容器、网络、挂载点
#docker-compose down # TODO: docker-compose: command not found. 不知道为什么找不到 docker-compose,但直接连接服务器执行就可以
/usr/local/python3/bin/docker-compose down
# 删除旧镜像
docker rmi yiyungent/simcaptcha
docker rmi yiyungent/simcaptcha-client
# 登录镜像服务器
docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}
# 创建并启动容器
#docker-compose up -d --build
/usr/local/python3/bin/docker-compose up -d --build

上面方法,每次 push 的均为 latest,没有版本号,不便于记录

区分版本

参考:

GitHub ghcr.io

Docker Hub

目标

我们想要让 拥有 tag 标记的成为一个 release 正式版或者 prerelease

这样的版本会被 push 到 Docker Hub,而没有 tag 的为开发版,但为了让少部分人能及时获取最新开发版,也 push 到 Docker Hub,如何区分?

正式版: v1.0.0

在这个 v1.0.0 后又更新了些,但不足以发布新版本,但仍 push 到 Docker Hub

于是由 GitHub Actions 自动 push 到 Docker Hub 标记为 上次版本号-beta,例如: v1.0.0-beta

意味: v1.0.0后的最新开发版(介于 v1.0.0 到 下一个正式版本之前)

下面分为两种情况,有时我们的一个仓库可能需要发布不止一个包,于是如果直接使用 v1.0.0 这样的 tag 会导致混乱,不知道这是哪一个包的版本,于是这里用 包名-v1.0.0, 例如 PluginCore-v1.0.0,来标记 PluginCorev1.0.0

这时 GitHub Actions 就需要去除 PluginCore-,再拿到 v1.0.0,用作 pushGitHub

Bug记录:

目前下方方案还有一个问题未解决: 那就是 获取上一次 releasetag,在一个仓库有多个包时,获取的 release tag,可能不是你所需要包的对应 tag,因此,为了防止混乱,-beta 是直接添加在获取到的上一次 release tag后,即release tag没有做去除包名,例如, 没有去除前面的 PluginCore-,而是直接用 PluginCore-v1.0.0

一个仓库发布一个包

docker-push-beta

UpyunAction-docker-push-beta.yml

UpyunAction-docker-push-beta.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
name: Docker Image CI/CD - Beta - UpyunAction

on:
push:
branches: [ main ]

jobs:
# build and push
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Get latest release
id: last_release
uses: InsonusK/get-latest-release@v1.0.1
with:
myToken: ${{ github.token }}
exclude_types: "release, prerelease"
view_top: 1

- name: Set outputs
id: vars
run: |
# 修改为你要推送的 镜像名
echo ::set-output name=IMAGE_NAME::upyun-action

- name: Build Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.last_release.outputs.tag_name }}-beta -f src/UpyunAction/Dockerfile .

- name: Login to Registry - Docker Hub
run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}

- name: Push Image - Docker
# push: last_release-beta
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.last_release.outputs.tag_name }}-beta

- name: Login to Registry - ghcr.io
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Push Image - ghcr.io
# push: last_release-beta
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.IMAGE_NAME }}
VERSION=${{ steps.last_release.outputs.tag_name }}-beta
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.last_release.outputs.tag_name }}-beta $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION

docker-push-release

UpyunAction-docker-push-release.yml

UpyunAction-docker-push-release.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
name: Docker Image CI/CD - Release - UpyunAction

on:
# release:
# types: [published]
push:
tags:
- 'v*'

jobs:
# build and push
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Set outputs
id: vars
run: |
#echo ::set-output name=RELEASE_VERSION::$(echo ${GITHUB_REF:10})
# 去掉前面的 refs/tags/
echo ::set-output name=RELEASE_VERSION::$(echo ${GITHUB_REF:10})
# 修改为你要推送的 镜像名
echo ::set-output name=IMAGE_NAME::upyun-action

- name: Build Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.RELEASE_VERSION }} -f src/UpyunAction/Dockerfile .

- name: Login to Registry - Docker Hub
run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}

- name: Push Image - Docker Hub
# push: RELEASE_VERSION, latest
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.RELEASE_VERSION }}
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.RELEASE_VERSION }} ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:latest
docker push ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:latest

- name: Login to Registry - ghcr.io
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Push Image - ghcr.io
# push: RELEASE_VERSION, latest
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.IMAGE_NAME }}
VERSION=${{ steps.vars.outputs.RELEASE_VERSION }}
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:latest $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
docker tag $IMAGE_ID:$VERSION $IMAGE_ID:latest
docker push $IMAGE_ID:latest


一个仓库发布多个包

docker-push-beta

AspNetCore3_1-docker-push-beta.yml

AspNetCore3_1-docker-push-beta.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
name: Docker Image CI/CD - Beta - AspNetCore3_1

on:
push:
branches: [ main ]

jobs:
# build and push
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Get latest release
id: last_release
uses: InsonusK/get-latest-release@v1.0.1
with:
myToken: ${{ github.token }}
exclude_types: "release, prerelease"
view_top: 1

- name: Set outputs
id: vars
run: |
# 修改为你要推送的 镜像名
echo ::set-output name=IMAGE_NAME::plugincore-aspnetcore3-1

- name: Build Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.last_release.outputs.tag_name }}-beta -f examples/AspNetCore3_1/Dockerfile .

- name: Login to Registry - Docker Hub
run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}

- name: Push Image - Docker
# push: last_release-beta
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.last_release.outputs.tag_name }}-beta

- name: Login to Registry - ghcr.io
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Push Image - ghcr.io
# push: last_release-beta
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.IMAGE_NAME }}
VERSION=${{ steps.last_release.outputs.tag_name }}-beta
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.last_release.outputs.tag_name }}-beta $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION

docker-push-release

AspNetCore3_1-docker-push-release.yml

AspNetCore3_1-docker-push-release.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
name: Docker Image CI/CD - Release - AspNetCore3_1

on:
# release:
# types: [published]
push:
tags:
- 'AspNetCore3_1-v*'

jobs:
# build and push
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Set outputs
id: vars
run: |
#echo ::set-output name=RELEASE_VERSION::$(echo ${GITHUB_REF:10})
# 去掉前面的 refs/tags/AspNetCore3_1-
# 注意: 这里需要修改 24,
# 这个数字为 `refs/tags/AspNetCore3_1-` 这个字符串的长度, 即为 24=10+len(AspNetCore3_1-)
echo ::set-output name=RELEASE_VERSION::$(echo ${GITHUB_REF:24})
# 修改为你要推送的 镜像名
echo ::set-output name=IMAGE_NAME::plugincore-aspnetcore3-1

- name: Build Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.RELEASE_VERSION }} -f examples/AspNetCore3_1/Dockerfile .

- name: Login to Registry - Docker Hub
run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}

- name: Push Image - Docker Hub
# push: RELEASE_VERSION, latest
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.RELEASE_VERSION }}
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:${{ steps.vars.outputs.RELEASE_VERSION }} ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:latest
docker push ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:latest

- name: Login to Registry - ghcr.io
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Push Image - ghcr.io
# push: RELEASE_VERSION, latest
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/${{ steps.vars.outputs.IMAGE_NAME }}
VERSION=${{ steps.vars.outputs.RELEASE_VERSION }}
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ steps.vars.outputs.IMAGE_NAME }}:latest $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
docker tag $IMAGE_ID:$VERSION $IMAGE_ID:latest
docker push $IMAGE_ID:latest

补充

docker-push-release.yml 触发运行时, docker-push-beta 并不会触发运行,(因此不用担心发布release时, 错误push两次)

因为 GitHub Actions 的触发条件互斥,

1
2
3
on:
push:
branches: [ main ]

上方并不意味着在所有 main 上的 push 都会触发,

因为有 同样的在有 tag: v* 时已经触发了,因此不会再触发 仅仅是 push 的条件

纠正,上诉说法有误

其实是因为 git pushgit push --tags 是两步,git push 触发一次, git push --tags 触发一次,

git push --tags 仅仅是推送了 tag,而不是源代码,因此仅仅触发了 GitHub Action 的 tag 触发器

git push,不会推送 tag,自然不会触发 tag触发器

GitHub 镜像仓库服务 ghcr.io

参考:

GitHub Container Registry 可免费用于公共镜像

在 Beta 版期间,Container Registry 可免费用于私有镜像,并且作为 GitHub Packages 的一部分,在普遍可用时将遵循相同的定价模型。

PS: 以前是 GitHub Packages Docker Registry,现在新的替换品是 GitHub Container Registry

它们都属于 GitHub Packages

域更改

Container registry 的域是 ghcr.io

注册表 示例 URL
GitHub Packages Docker Registry docker.pkg.github.com/OWNER/REPOSITORY/IMAGE_NAME
Container registry ghcr.io/OWNER/IMAGE_NAME

注意

对于 ghcr.io 而言,与 Docker Hub 不同,无需提交 latestlatest 标签将始终指向最新提交的镜像,

因此,在使用 GitHub Actions 自动构建时,无需 push xxx:latest

而在 Docker Hub, latest只是在你没有指定 :tag 时默认即为 latest

Docker Hub

ghcr.io

注意

测试了一下,发现好像又不一定 latest 始终指向最新,因此最好还是 release 情况下,latest 和指定版本号 的 docker imagepush 一次

补充

docker-compose CLI

docker-compose build

1
build              Build or rebuild services

docker-compose build --pull

1
--pull                  Always attempt to pull a newer version of the image.

docker-compose pull

1
pull               Pull service images

docker-compose up

1
up                 Create and start containers

其实直接一个up 就可以,如果没有build,则自动build,但是并不是每次都会build,如果已经存在镜像,则不build,如果要每次都build,则 up --build

docker-compose down

1
down               Stop and remove containers, networks, images, and volumes

注意:虽然文档说是会删除镜像,但实际测试,使用 docker images,依然会显示有镜像,不过,确实相关容器被停止并删除

docker-compose push

1
push               Push service images

示例:

1
docker-compose -f docker-compose.Debug.yml up -d

CentOS 用户,组

参考:

添加用户 deploy-docker

1
adduser deploy-docker

补充:

在新建用户的同时添加到 docker组

1
adduser -g docker deploy-docker

为此用户设置密码

1
passwd deploy-docker

给已有用户增加组

1
usermod -G docker deploy-docker

或者

1
gpasswd -a username groupname

注意:

添加用户到某一个组

可以使用usermod -G groupname username这个命令可以添加一个用户到指定的组,但是以前添加的组就会清空掉。

所以想要添加一个用户到一个组,同时保留以前添加的组时,请使用gpasswd这个命令来添加操作用户

查看当前用户所在组

1
groups

查看指定用户所在组

1
groups username

查看有哪些组

1
cat /etc/group

查看有哪些用户

1
cat /etc/passwd

ERROR: Failed to Setup IP tables

1
2
3
4
5
[deploy-docker@iZqnwheobg66nbZ simcaptcha]$ docker-compose up -d --build
Building with native build. Learn about native build in Compose here: https://docs.docker.com/go/compose-native-build/
Creating network "simcaptcha_simcaptcha-net" with driver "bridge"
ERROR: Failed to Setup IP tables: Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait -t nat -I DOCKER -i br-f34f4bfebf69 -j RETURN: iptables: No chain/target/match by that name.
(exit status 1))

重启 Docker

1
service docker restart

参考

感谢帮助!