GitHub Container Registry(ghcr)でコンテナイメージを管理する

## はじめに

この記事ではコンテナイメージをビルドし、GitHub Container Registry(以下、ghcr)にpushする手順を整理します。 コマンドラインを使う方法とGitHub Actionsを使う方法の2つを紹介します。

## ghcrとは

GitHubが提供しているコンテナレジストリです。

他のコンテナレジストリの選択肢としてはAmazon ECR・Google Container Registry、Azure Container Registry等が挙げられます。 これらと比較してghcrは「GitHub Actionsとの連携が容易」「クラウドベンダーのリソースやトークンを管理する手間が不要」という優位点があります。

GitHubを使っているプロジェクトではコンテナレジストリとしてgchrが有力な候補になるでしょう。

## ① コマンドラインでコンテナイメージをpushする

1. Personal Access Token(PAT)を発行する
1. ghcrにログインする
1. コンテナイメージをビルドする
1. コンテナイメージをpushする

### 1. Personal Access Token(PAT)を発行する

ghcrはPersonal Access Token(以下PAT)による認証のみがサポートされています。
[personal access token (classic) の作成手順](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#personal-access-token-classic-%E3%81%AE%E4%BD%9C%E6%88%90)
に沿ってトークンを発行します。

https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#personal-access-token-classic-%E3%81%AE%E4%BD%9C%E6%88%90

:::note alert
作成後にトークンの値を確認することはできないため、必ずトークンの値を控えてください
:::

ghcrにコンテナイメージをpushするには「write:packages」権限を適用する必要があります。


![GitHub_PAT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598747/3dd86d45-7a31-2394-54bb-afcea252ebd7.png)

### 2. ghcrにログインする

コンテナイメージをghcrにpushするためには事前にghcrにログインする必要があります。
ログインには先ほど作成したPATを使います。

今回は`~/.ghcr_token.txt`にトークン文字列が書き込まれているという体で手順を実行します。

```bash
$ cat ~/.ghcr_token.txt | docker login ghcr.io -u USERNAME --password-stdin
Login Succeeded
```

`Login Succeed`と表示されれば成功です。

### 3. コンテナイメージをビルドする

今回はコンテナイメージをpushすることが目的なので、どんなイメージでも問題ありません。
ここでは適当にPythonのイメージを用意します。

```
.
|- app.py
|- Dockerfile
```

```python:app.py
print("Hello, World!")
```
```Dockerfile:Dockerfile
FROM python:3.12-slim

COPY app.py .
CMD ["python", "./app.py"]
```

```bash
$ docker build -t python-test .
[+] Building 0.1s (7/7) FINISHED                                                                     docker:default
 => [internal] load build definition from Dockerfile                                                           0.0s
 => => transferring dockerfile: 102B                                                                           0.0s 
 => [internal] load .dockerignore                                                                              0.0s 
 => => transferring context: 48B                                                                               0.0s 
 => [internal] load metadata for docker.io/library/python:3.12-slim                                            0.0s 
 => [internal] load build context                                                                              0.0s 
 => => transferring context: 27B                                                                               0.0s 
 => [1/2] FROM docker.io/library/python:3.12-slim                                                              0.0s 
 => CACHED [2/2] COPY app.py .                                                                                 0.0s 
 => exporting to image                                                                                         0.0s 
 => => exporting layers                                                                                        0.0s 
 => => writing image sha256:7e21e1f0d2428688c81735807b66e5db23b2dc8121a3bebe48533078aee7b517                   0.0s 
 => => naming to docker.io/library/python-test                                                                 0.0s 
```

### 4. コンテナイメージをpushする

それでは先ほどビルドしたイメージをpushします。

ghcrにコンテナイメージをpushするためには`ghcr.io/<USERNAME>/<IMAGENAME>:TAG`の形式でタグ付けする必要があります。GitHubのユーザ名がjunpei-azumaでイメージ名がpython-test, タグ名を1.0としたい場合、以下のようになります。

また、CLIからイメージをpushする場合、リポジトリと紐づけることができないことに注意が必要です。[^1]

```bash
$ docker tag python-test:latest ghcr.io/junpei-azuma/python-test:1.0.0
```

それではコンテナイメージをpushします。


```bash
$ docker push ghcr.io/junpei-azuma/python-test:1.0.0
The push refers to repository [ghcr.io/junpei-azuma/python-test]
2b6d15ba5b96: Pushed
6258d28909ff: Layer already exists
cfaf35e1cecf: Layer already exists
c6bc67c64ae2: Layer already exists
bfc9081d1eb2: Layer already exists
1f00ff201478: Layer already exists
1.0.0: digest: sha256:7ad574ae5599e33b8c100ebf5376ced4eae97b1bafe1e549751ea8168cec5651 size: 1577
```

pushしたバージョンのイメージが作成されていれば成功です。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598747/54bdeb32-6953-8609-18df-99979b06cdb1.png)

## ② GitHub Actionsでコンテナイメージをpushする

1. ghcrにログインする
1. コンテナイメージをビルド・pushする


①と比較してこの方法には以下のメリットがあります。そのため、実運用ではGitHub Actionsを使う方法が一般的です。

1. 毎回コマンドを手打ちする必要がない
1. イメージをリポジトリと紐づけることができる
1. データ転送に課金されない[^2]

今回は「GitHubでタグを新しく作成するとコンテナイメージをビルド→pushする」という処理を行うワークフローを実装します。

```yml:build-image.yml
name: Build And Push Container Image

on:
  push:
    tags:
      - "*"

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: python-test
jobs:
  build-and-push:
    runs-on: ubuntu-latest
    # イメージをプッシュする権限をGITHUB_TOKENに付与する
    permissions:
      contents: read
      packages: write
    steps:
    - name: Checkout
      uses: actions/checkout@v4
    # 1. ghcrにログインする
    - name: Login to GitHub Container Registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    # 2. コンテナイメージをビルド・pushする
    - name: Build and Push Container
      uses: docker/build-push-action@v5
      with:
        push: true
        tags: ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} 
```

### 1. ghcrにログインする

CLを使う場合と同じく最初にghcrにログインします。
自前でスクリプトを書いても良いのですが、今回はGitHub Market Placeで提供されている`docker-login`アクションを使います。

https://github.com/marketplace/actions/docker-login

こちらのアクションはthird party製ではありますが、dockerが開発しているため公式製と同程度に信頼できると見てよいでしょう。

#### パラメータ

このアクションは下記のパラメータを受け取ります。

| 名前 | 型 | デフォルト値 | 概要 |
|----------|--------|---------|------------------------------------------------------------------------------------------------------|
| registry | String |docker.io|コンテナレジストリのアドレス。|
| username | String |         |コンテナレジストリのユーザ名<br>(今回の場合、GitHubのユーザ名)|
| password | String |         |コンテナレジストリの認証用パスワード・トークン|
| ecr      | String | auto    |コンテナレジストリがECRかどうか|
| logout   | Bool   | true    | ジョブの終了後にコンテナレジストリからログアウトするかどうか|

今回は`registry`,`username`,`password`の3つを指定します。


* registry: `ghcr.io`
* username: ${{ github.actor }} (ワークフローの実行ユーザ) [^3]
* password: ${{ secrets.GITHUB_TOKEN }}(ワークフローの起動時に生成される一意なトークン)

公式マニュアルの実装例は[こちら](https://github.com/marketplace/actions/docker-login#github-container-registry)


### 2. コンテナイメージをビルド・pushする

続いてコンテナイメージをビルド・pushします。ここでもGitHub Market Placeの`build-push-action`アクションを使います。

https://github.com/marketplace/actions/build-and-push-docker-images

#### パラメータ

数が多く、全部は書いていられないのでよく使われるものだけ記載します。


| 名前 | 型 | 概要 |
|------------------|-------------|-------------------------------------------------------------------------------------------------|
| context          | String      |ビルドを実行するカレントディレクトリ. デフォルト値はリポジトリのルートパス|
| file             | String      |Dockerfileのパス. デフォルト値は{context}/Dockerfile|
| push             | Bool| コンテナレジストリにpushするかどうか(デフォルト値はfalse)|
| tags             | List/CSV    | タグの値のリスト|

今回は`push`と`tags`を指定します。

* push: コンテナレジストリにイメージをpushするのでtrue
* tags: `ghcr.io/<REPOSITRYNAME>/<USERNAME>/<IMAGENAME>:<TAGNAME>` となるように指定

### 3. ワークフローを実行する

それではワークフローを実行します。
mainブランチにpushして適当にタグを打ちます。


![push_tag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598747/61ea8510-4db9-7f28-cc76-a376aa7c66f2.png)

コンテナイメージが追加されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598747/5fa0872c-a58e-f80d-d731-e71761aeb6b2.png)


## おわりに

今回はGitHub Container Registryにコンテナイメージをpushする方法をまとめました。
GitHub内でビルド~push~保存が完結するので運用しやすいと思いました。



## 参考資料

https://docs.github.com/ja/packages/working-with-a-github-packages-registry/working-with-the-container-registry#pushing-container-images

https://docs.github.com/ja/actions/security-guides/automatic-token-authentication

https://docs.github.com/ja/billing/managing-billing-for-github-packages/about-billing-for-github-packages

https://docs.github.com/ja/actions/learn-github-actions/contexts

https://github.com/marketplace/actions/docker-login


[^1]: [GitHub Docs コンテナイメージをプッシュする](https://docs.github.com/ja/packages/working-with-a-github-packages-registry/working-with-the-container-registry#pushing-container-images)
[^2]: [GitHub Packages の課金について](https://docs.github.com/ja/billing/managing-billing-for-github-packages/about-billing-for-github-packages#github-packages-%E3%81%AE%E8%AA%B2%E9%87%91%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
[^3]: [コンテキスト](https://docs.github.com/ja/actions/learn-github-actions/contexts)