GitHub ActionsでDockerコンテナをGitHub Package RegistryやDocker Hubにpushした話

この記事はお試しで [自分のブログ](https://blog.fagai.net/2020/09/08/github-actions-package-registry-push/) と同じ内容を投稿しています。

GitHub ActionsでDockerコンテナをGitHub Package Registryにpushした話をしようと思います。

## 想定読者

* dockerのビルドがなんとなくわかる

## 背景

現在私は会社のベースイメージとして使えるように

https://github.com/fagai/docker-php

というgithubリポジトリでDocker Hubにautomated buildを設定してDocker Hubからビルドを行っていました。
Docker Hubのビルドタイムラインは非常に遅くて、ちょいちょいストレスになりつつもなんとか続けていました。

ただ、最近になってDocker Hubのpull制限だったりとか、GitHubがContainer Registryがパブリックベータになったりしてどうにかしたいなぁと思いまして、GitHub ActionsでBuild+Pushすることにしました。

## GitHub Container Registry(GCR)とGitHub Package Registry(GPR)は違う

ここから、GitHub Container RegistryをGCRと省略します、決してGoogle Container Registryではないですので注意してください。(略が被るの困るね)

前者は先日パブリックベータとして出たほうで、後者は少し前からあったものです。
2つの違いは

* URLが異なる(ghcr.ioとdocker.pkg.github.com)
* ユーザ(or Organization)に紐づくか、Repositoryに紐づくかが違う

といったところです。

紐づく部分に関してはURLのパスも以下のように違います。

* ghcr.io/OWNER/IMAGE_NAME
* docker.pkg.github.com/OWNER/REPOSITORY/IMAGE_NAME

こんな感じでユーザに紐づくかリポジトリに紐づくか違います。
まあ、以下に書いていく内容はどちらでも同じように書くことが可能なので、Package Registryなんだ・・・ってなっても見ると良いと思います。


もともと私はGitHub Container Registryを使おうとしてましたが、ドメインがghcr.ioとなっておりなんだろうこれ・・・GitHubじゃないんじゃね・・・？とか思っちゃってGitHub Package Registryを使いだした次第です。
現状で言えばGCRのほうがmanifestのバージョンが新しかったりして使う分にはGCRのほうがいいと思います。パッケージに紐付けたいときはGPRを利用しましょう。

## 最初やったとき

最初はstarter-workflowのdocker publishを使ってました。

https://github.com/actions/starter-workflows/blob/master/ci/docker-publish.yml

当時のやつ
https://github.com/fagai/docker-php/blob/32988eacf16e697e5376ee0af778079fa1e0fb62/.github/workflows/docker-publish.yml

DockerHubと比べてQueueに積まれる時間もないのでめちゃくちゃ良かったです。

## 次第に

次第にDockerHubにもpushできないかと思って、探した結果 [https://github.com/docker/build-push-action](https://github.com/docker/build-push-action) を見つけてこちらに書き換えることに。

https://github.com/docker/build-push-action

当時の：https://github.com/fagai/docker-php/blob/12d060efd0b91924f0edf85f3b127e19d47cd18a/.github/workflows/docker-publish.yml

```yml
- name: Push to GitHub Packages
  uses: docker/build-push-action@v1
  with:
    dockerfile: ${{ matrix.images }}/Dockerfile
    username: ${{ github.actor }}
    password: ${{ secrets.CR_PAT }}
    registry: docker.pkg.github.com
    repository: fagai/docker-php/${{ env.IMAGE_NAME }}
    tags: ${{ env.IMAGE_VERSION }}
```

スリム～～。
CR_PATの箇所ですが、Container Registry Personal Access Tokenとかの略なんでしょうか、実はstarter-workflowのときにそう書いてあったのでその名前でgithubのtokenをセットしました。(github.tokenでも良いのですが、access tokenをできる限り絞ったほうが良いらしくaccess repositoryとwrite repositoryだけの権限をつけました)

後にDocker Hubのpushの設定もつけました。

```yml
- name: Push to Docker Hub
  uses: docker/build-push-action@v1
  with:
    dockerfile: ${{ matrix.images }}/Dockerfile
    username: ${{ github.actor }}
    password: ${{ secrets.DOCKER_HUB_TOKEN }}
    repository: fagai/${{ env.IMAGE_NAME }}
    tags: ${{ env.IMAGE_VERSION }}
```

[build-push-action](https://github.com/docker/build-push-action)の例ではpasswordを設定するようになっていましたが、Docker Hubにもaccess tokenがあるのでそっちを設定してからつけました。

https://docs.docker.com/docker-hub/access-tokens/

## キャッシュとか色々ちゃんとしたい

現状のままではキャッシュを参考にしてくれません。(ただ、2回目のbuild-push-actionはcacheを元にしてくれるので片方でbuild+pushしてしまえばもう片方はすぐにpush出来ます)

[build-push-action](https://github.com/docker/build-push-action)に`cache_froms`というパラメータがあるのでこれをうまく使いたいです。

色々試していく結果、`cache_froms`を利用するためには`Buildkit`を使う必要があることがわかりました。
また、`build_args: BUILDKIT_INLINE_CACHE=1`をセットしないといけないことも判明。
どうやら`cache_froms`はドメイン部分も指定しないとダメっぽい雰囲気でした。←ここ結構悩んだ

ということでこんな感じになりました。

```yml
- name: Push to GitHub Packages
  uses: docker/build-push-action@v1
  env:
    DOCKER_BUILDKIT: 1
  with:
    dockerfile: ${{ matrix.images }}/Dockerfile
    username: ${{ github.actor }}
    password: ${{ secrets.CR_PAT }}
    registry: docker.pkg.github.com
    repository: fagai/docker-php/${{ env.IMAGE_NAME }}
    tags: ${{ env.IMAGE_VERSION }}
    build_args: BUILDKIT_INLINE_CACHE=1
    cache_froms: docker.pkg.github.com/fagai/docker-php/${{ env.IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
```

これで行ける！と思ったのですが

キャッシュされてない・・・となりまして、Actionsのほうを見てみると

```
#4 importing cache manifest from docker.pkg.github.com/fagai/docker-php/php...
#4 ERROR: httpReaderSeeker: failed open: could not fetch content descriptor sha256:65d9f276544048f140bb1a1cceea52f86e7e704b351c56b8d6b9f18c5e9c0e4d (application/vnd.docker.distribution.manifest.v2+json) from remote: not found
```

こんな感じでエラーが出てキャッシュが取得できずにそのままビルドが走ってしまっていました。
どうやらGitHub Package Registryのほうはdockerの新しいmanifestにまだ対応してないようです。(GitHub Container Registryのほうは対応している様子)

https://github.community/t/handle-multi-arch-docker-images-on-github-package-registry/14314



ということで、先にdocker Hubの方からビルドすることにしました。
cache_fromsはdocker.ioから書かないとダメです。

```yml
- name: Push to Docker Hub
  uses: docker/build-push-action@v1
  env:
    DOCKER_BUILDKIT: 1
  with:
    dockerfile: ${{ matrix.images }}/Dockerfile
    username: ${{ github.actor }}
    password: ${{ secrets.DOCKER_HUB_TOKEN }}
    repository: fagai/${{ env.IMAGE_NAME }}
    tags: ${{ env.IMAGE_VERSION }}
    build_args: BUILDKIT_INLINE_CACHE=1
    cache_froms: docker.io/fagai/${{ env.IMAGE_NAME }}:${{ env.IMAGE_VERSION }}
```

ということでこんな感じになります。

```
#4 importing cache manifest from docker.io/fagai/php:7.2-alpine-fpm
#4 DONE 0.2s
```

その後の処理もCACHEDという表示がされ、ビルドが動かずにキャッシュをベースにしていることがわかります。

## その後の後付け

```yml
- name: cancel old workflow
  uses: styfle/cancel-workflow-action@0.4.1
  with:
    access_token: ${{ github.token }}
```

これは過去にやったことがありました。過去のActionをキャンセルしてくれるActionです。
何度もcommitしたときに過去のActionがずっと残って動いてしまうことを解決してくれます。

また、scheduleもセットしまして、毎週月曜日にActionが動くようにしました。

```yml
on:
  push:
    branches:
      - master
  pull_request:
  # 定期更新をやる(毎週月曜日)
  schedule:
    - cron:  '0 0 * * 1'
```

## 最後に

GitHub Actionsにbuildとpushをするようにした結果、Docker HubがずっとQueueで待ちになる問題も解決されたし、何より並列でビルドができるので短時間でpushするように出来ました。

[build-push-action](https://github.com/docker/build-push-action)はv2で[buildx](https://docs.docker.com/buildx/working-with-buildx/)というDocker 19.03で新しく追加されたビルドが利用されるようになるみたいです。
BuildKitは半公式みたいな感じでBuildxが公式のマルチCPUビルド対応の機能みたいです。
macとかでは`docker buildx install`と打つことで`docker build`へのエイリアスが貼られて勝手にbuildxを使ってくれるようになります。いいね。