Docker Hub代わりのGitHub Container Registry入門

:::note warn
一生懸命まとめましたが、[公式ドキュメント](https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-github-packages)に丁寧で正確な説明があることに最近気づきました。
:::

# 概要
**Docker Hub**の無料枠に制限がかかるという話もあり、**GitHub Container Registry**の使い方を調べたのでメモする。具体的には以下を実現したい。

1. **GitHub**へpushする度、**GitHub Actions**でdockerイメージをbuildし、**GitHub Container Registry**にpush
2. その際、**GitHub**へpushしたタグに応じて、dockerイメージのタグも変更する

# 環境
- wsl2のDebian
- Docker version 19.03.12, build 48a66213fe

# 事前準備
GitHub Container Registryに自動でdockerイメージをpushするには、**Personal Access Token**が必要[^1]。GitHub右上の自分のアイコンから`Settings` > `Developer settings` > `Personal access tokens` > `Generate new token`の手順で作成する。スコープは以下の画像のもので大丈夫そう。
[^1]: Github Packagesでは、代わりに[GITHUB_TOKEN](https://docs.github.com/ja/actions/configuring-and-managing-workflows/authenticating-with-the-github_token)が使えたらしい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540947/f1ab192f-81fb-953c-8421-d20b81312a8d.png)

得られたトークンはDockerfileなどを管理しているGitHubリポジトリで`Setting` > `Secrets` > `New secret`の手順で登録しておく（ここでは`CR_PAT`という名前で保存したことにする）。

# YAMLファイル作成①
GitHub Actionsを制御するためのYAMLファイルを作成する。以下のようなディレクトリ構成を想定[^2]。

```
.
├── .git
├── .github
│   └── workflows
│       └── build_on_push.yaml
└── Dockerfile
```

[^2]: 実際に使ったGitHubリポジトリは[こちら](https://github.com/dr666m1/qiita_starwars)
`build_on_push.yaml`の内容は以下の通り。

```yaml
name: Publish Docker image
on: push
jobs:
  main:
    name: Push Docker image to Github Container Registry
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.CR_PAT }}
      - name: Push to GitHub Container Registry
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: ghcr.io/dr666m1/qiita_starwars:latest
```
`name:`はworkflowやjobを命名しているだけなので任意の値。`on:`ではトリガーとなるイベントを指定（ここではpush）。3行目の`jobs:`で5つのstepからなるjobを定義しているので、以降で各stepについて簡単に解説する（ちなみに`runs-on:`はjobを実行する仮想マシンの指定で、ここでは[GitHubホストランナー](https://docs.github.com/ja/actions/reference/virtual-environments-for-github-hosted-runners)を選択している）。

### Checkout
`actions/checkout@v2`というactionで、GitHubリポジトリにアクセスできる状態にする。逆に言えば、このstepがないと「Dockerfileが見つかりません」といったエラーに陥るはず。ドキュメントは[こちら](https://github.com/marketplace/actions/checkout)。

### Set up QEMU, Set up Docker Buildx
`docker/setup-qemu-action@v1` `docker/setup-buildx-action@v1`はいずれも必要な機能を利用可能にするためのactionという程度の認識でよいと思う。

### Login to GitHub Container Registry
`docker/login-action@v1`というactionでログイン処理を行う。`${{ secrets.CR_PAT }}`は先ほど作成したPersonal Access Tokenを参照している。Docker Hub やGitLabにも対応していて、[ドキュメント](https://github.com/marketplace/actions/docker-login)にはそれぞれの対応方法が記載されている。

### Push to GitHub Container Registry
`docker/build-push-action@v2`というactionでdockerイメージのbuildとpushを行う。`tags:`でタグも指定できるが、この段階では`latest`としておく。

# 動作確認
ファイルの準備ができたら`git push`するだけで、YAMLで定義したワークフローが実行される。以下の画面のようにログも確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540947/547e06b4-02f1-d54c-87ec-53c541590e4a.png)

問題なければGitHub右上の自分のアイコンから`Your profile` > `Packages`でbuildされたdockerイメージを確認できる。デフォルトでprivateになるようなので、publicにしたければ変更してしまう。あとは`docker run`して問題なければOK[^3]。

```sh
docker run -it --rm ghcr.io/dr666m1/qiita_starwars:latest
```
[^3]: ~~[Dockerfile](https://github.com/dr666m1/qiita_starwars/blob/master/Dockerfile)を見ての通りだが、この例だとスターウォーズの上映が始まる（`telnet towel.blinkenlights.nl`）ので`Ctrl+]` > `quit`で抜ける~~このサンプルdockerイメージは削除済み

# YAMLファイル作成②
次にタグを指定できるよう、YAMLファイルを以下の通り変更する。

```yaml
name: Publish Docker image
on: push
jobs:
  main:
    name: Push Docker image to Github Container Registry
    runs-on: ubuntu-latest
    steps:
      - name: Prepare # 追加
        id: prep
        run: |
          if [[ $GITHUB_REF == refs/tags/* ]]; then
            TAG=${GITHUB_REF#refs/tags/}
          else
            TAG="latest"
          fi
          echo "::set-output name=tag::${TAG}"
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.CR_PAT }}
      - name: Push to GitHub Container Registry
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: ghcr.io/dr666m1/qiita_starwars:${{ steps.prep.outputs.tag }} # 変更
```
主な変更点はPrepareというstepの追加。`run:`でshell（デフォルトはbash[^4]）で実行されるコマンドを指定する。ここでは`$GITHUB_REF`環境変数[^5]によって処理を変えていて、タグの名前を含めばそれを、なければ`latest`を`TAG`に代入している。`echo "::set-output name=tag::${TAG}"`は[ワークフローコマンド](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions)と呼ばれるもので、ここでは`TAG`の値を後続のstepで参照できるようにしている。`${{ steps.prep.outputs.tag }}`が実際に参照している部分。

[^4]: 利用可能なshellは[公式ドキュメント](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell)を参照
[^5]: 利用可能な環境変数は[公式ドキュメント](https://docs.github.com/ja/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables)を参照

この変更によって、GitHubにpushしたタグに応じてdockerイメージのタグも変更されるようになる。例えば以下のように実行すれば、dockerイメージのタグも`1.0`になる。

```bash
git tag 1.0
git push --tags
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540947/2d540843-93bb-b57f-b78b-64696bc13a05.png)

# 最後に
もしDocker HubからGitHub Container Registryに移行したくなっても、これで概ね困らなそう。GitHub Actionsは自動テストなど、もっといろいろなことに使えるはずなので、時間があるときに勉強したい。