Featured image of post Github Actionsを使ってDocker ImageをGitHub Container RegistryにPushする

Github Actionsを使ってDocker ImageをGitHub Container RegistryにPushする

CI/CDっぽいことがしたい

GitHub公式のCI/CDサービスGitHub Actionsを使って, リポジトリ上のDockerfileからimageをbuildしてpushする手順を試した.


まとめ

GitHubのPAT(個人アクセストークン)をリポジトリのSecretsCR_PATとして登録した状態で以下のような.github/workflows/docker-publish.ymlを作成すると,
masterブランチへのcommitやrelease(tag)の作成時にDocker imageGitHub ActionGitHub Container Registryにpushすることができる.
pushしたimage : https://github.com/users/uzimihsr/packages/container/package/echo

# ghcr.io/<GitHubアカウント>/<image名>:<タグ>のimageをGitHub Packagesにpushするworkflow
name: Docker

on:
  # masterブランチまたはvから始まるtag(例:`v1.2.3`)のpushでjobsを実行する
  push:
    branches:
      - master
    tags:
      - v*
  # 全ブランチのPRに対してもjobsを実行
  pull_request:
env:
  # <image名>を指定
  IMAGE_NAME: echo
jobs:
  # テスト用のjob
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run tests
        run: |
          if [ -f docker-compose.test.yml ]; then
            docker-compose --file docker-compose.test.yml build
            docker-compose --file docker-compose.test.yml run sut
          else
            docker build . --file Dockerfile
          fi          
  # imageをpushする
  push:
    # test jobが成功した場合のみトリガーされる
    needs: test
    runs-on: ubuntu-latest
    # pushイベント以外(PRなど)では実行されない
    if: github.event_name == 'push'
    steps:
      - uses: actions/checkout@v2
      - name: Build image
        run: docker build . --file Dockerfile --tag $IMAGE_NAME
      - name: Log into GitHub Container Registry
        # `read:packages`と`write:packages`の権限を持つPAT(個人アクセストークン)をSecretに`CR_PAT`として登録しておく
        # PATの作成手順 : https://docs.github.com/ja/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token
        run: echo "${{ secrets.CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin
      - name: Push image to GitHub Container Registry
        run: |
          # <GitHubアカウント>が自動で選択される
          IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
          IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
          VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
          # git tagの値を<タグ>として付与
          [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
          # masterブランチへのcommitでトリガーされている場合はlatestを<タグ>として使う
          [ "$VERSION" == "master" ] && VERSION=latest
          echo IMAGE_ID=$IMAGE_ID
          echo VERSION=$VERSION
          docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
          docker push $IMAGE_ID:$VERSION          

https://github.com/uzimihsr/echo-image/blob/master/.github/workflows/docker-publish.yml

環境

やりかた

PATの発行とログイン

まずは公式ドキュメント1を参考に, GitHub Container Registryの認証に必要なPAT(個人アクセストークン)を発行する.

GitHubにログインした状態で
https://github.com/settings/tokens
を開き, Generate new tokenをクリック.
GitHubの設定画面

PATの権限設定画面ではwrite:packages, read:packagesにのみチェックを入れてGenetate tokenをクリック.
PATの作成

PATが発行される.
発行されたPAT

発行されたPATを使って, コマンドラインからGitHub Container Registryにログインする.

# GitHub Container Registryにログイン
$ echo <PAT> | docker login ghcr.io -u <GitHubアカウント> --password-stdin
Login Succeeded

これでログインは成功.

手動でpush

上記の手順でGitHub Container Registryにログインした状態で, まずはコマンドラインからimageをpushしてみる.

適当なimageを作成.

# 適当なディレクトリで適当なDockerfileを作成
$ cd /path/to/workspace
$ touch Dockerfile

# Docker imageをビルド, 動作確認
$ docker image build -t echo:latest .
$ docker container run --rm echo:latest
hello, world!

次にこのimageGitHub Container Registry用のタグをつけ, pushする.

# imageのタグをつけ直す
$ docker image tag echo:latest ghcr.io/uzimihsr/echo:latest

# Docker imageをpush
$ docker image push ghcr.io/uzimihsr/echo:latest
The push refers to repository [ghcr.io/uzimihsr/echo]
be8b8b42328a: Pushed
latest: digest: sha256:c7926eae1c1aef6291e5eef1673c9815b5644fa9c417bfab65a4baba50c046a2 size: 527

pushしたimageの情報はGitHub PackagesのUIから確認できる.
https://github.com/users/uzimihsr/packages/container/package/echo

pushしたimage

もちろんこのimageをpullして使うこともできる.

# ローカルのimageを削除
$ docker image rm ghcr.io/uzimihsr/echo:latest

# imageをpull
$ docker image pull ghcr.io/uzimihsr/echo:latest
latest: Pulling from uzimihsr/echo
Digest: sha256:c7926eae1c1aef6291e5eef1673c9815b5644fa9c417bfab65a4baba50c046a2
Status: Downloaded newer image for ghcr.io/uzimihsr/echo:latest
ghcr.io/uzimihsr/echo:latest

# 動作確認
$ docker container run --rm ghcr.io/uzimihsr/echo:latest
hello, world!

これでGitHub Container Registryを使ったimageのpush/pullができるようになった.

GitHub Actionsでpush

次にGitHub Actionsを使ってimageGitHub Container Registryにpushしてみる.

新たにGitHubリポジトリを作成し, 先程作成したDockerfileをgit pushする.
作ったリポジトリ : https://github.com/uzimihsr/echo-image

# Dockerfileが存在する状態
$ ls
Dockerfile

# git initからpushまで
$ git init
$ git remote add origin https://github.com/uzimihsr/echo-image.git
$ git add .
$ git commit -m "initial commit"
$ git push origin master

リポジトリのActionsタブを開き, Publish Docker ContainerアクションのSet up this workflowをクリック.
GitHub Actions

workflowの定義ファイルdocker-publish.ymlを編集する画面が開く.
このままでも動くけど, 自分用にenv.IMAGE_NAMEだけ任意のimage名に修正する.

docker-publish.yml
# ghcr.io/<GitHubアカウント>/<image名>:<タグ>のimageをGitHub Packagesにpushするworkflow
name: Docker

on:
  # masterブランチまたはvから始まるtag(例:`v1.2.3`)のpushでjobsを実行する
  push:
    branches:
      - master
    tags:
      - v*
  # 全ブランチのPRに対してもjobsを実行
  pull_request:
env:
  # <image名>を指定
  IMAGE_NAME: echo
jobs:
  # テスト用のjob
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run tests
        run: |
          if [ -f docker-compose.test.yml ]; then
            docker-compose --file docker-compose.test.yml build
            docker-compose --file docker-compose.test.yml run sut
          else
            docker build . --file Dockerfile
          fi          
  # imageをpushする
  push:
    # test jobが成功した場合のみトリガーされる
    needs: test
    runs-on: ubuntu-latest
    # pushイベント以外(PRなど)では実行されない
    if: github.event_name == 'push'
    steps:
      - uses: actions/checkout@v2
      - name: Build image
        run: docker build . --file Dockerfile --tag $IMAGE_NAME
      - name: Log into GitHub Container Registry
        # `read:packages`と`write:packages`の権限を持つPAT(個人アクセストークン)をSecretに`CR_PAT`として登録しておく
        # PATの作成手順 : https://docs.github.com/ja/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token
        run: echo "${{ secrets.CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin
      - name: Push image to GitHub Container Registry
        run: |
          # <GitHubアカウント>が自動で選択される
          IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
          IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
          VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
          # git tagの値を<タグ>として付与
          [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
          # masterブランチへのcommitでトリガーされている場合はlatestを<タグ>として使う
          [ "$VERSION" == "master" ] && VERSION=latest
          echo IMAGE_ID=$IMAGE_ID
          echo VERSION=$VERSION
          docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
          docker push $IMAGE_ID:$VERSION          

https://github.com/uzimihsr/echo-image/blob/master/.github/workflows/docker-publish.yml

docker-publish.ymlの編集が終わったら, Start commit -> Commit new fileと進みworkflowを作成する.
workflowの設定

次にこのworkflowGitHub Container Registryの認証を行うためのSecretsの設定をする.

リポジトリのSettingsタブを開き, New sectetをクリック.
Secretの設定画面

Secretの作成画面ではNameCR_PAT(docker-publish.yml内で参照している名前)に設定し, Valueには先程作成したPATを貼り付けてAdd secretをクリック.
Secretの作成

Secretが作成された.
作成されたSecret

Secretを作成した状態でリポジトリのActionsタブを開くと,
先程のdocker-publish.yml作成時のcommitで起動したworkflowが失敗している.
(CR_PATSecretを作る前に実行されたため, 認証部分でコケている)
Re-run all jobsから再度workflowを実行してみる.
リトライする

今度はCR_PATが作成済みなのでworkflowが正常に終了し, imageghcr.io/uzimihsr/echo:latestにpushされた.
できた

再度GitHub Packageの画面
https://github.com/users/uzimihsr/packages/container/package/echo
を開くと, 確かにLast publishedが更新されている.
publishされたimage

以上でGitHub Actionsを使ってimageGitHub Container Registryにpushする設定ができたので, 試しにimageを更新してみる.

# masterからhogeブランチを切る
$ git checkout master
$ git pull origin master
$ git checkout -b hoge

# Dockerfileを修正
$ vim Dockerfile
$ cat Dockerfile
FROM busybox

ENTRYPOINT [ "echo" ]

CMD [ "good morning!" ] # CMDを変更した

# commitしてhogeブランチをpush
$ git add ./Dockerfile
$ git commit -m "fixed Dockerfile: CMD"
$ git push origin hoge

Dockerfileの修正commitを積んだhogeブランチがpushされた状態でPR(master <- hoge)を作成すると, PRにトリガーされたworkflowが実行される.
testjobのみが実行されpushjobがスキップされているが, これは今回作成したdocker-publish.ymlGitHub eventがpushの場合にのみ実行するよう設定しているため.

if: github.event_name == 'push'

PRでの動作

このPRをmergeすると今度はmasterブランチへのpushが行われるので, それにトリガーされたworkflowtestpushjobが実行される.
以降のimageがpushされるまでの流れは先程試した流れと同じで, latestタグのimageがpushされる.

latestタグだけではimageのバージョン管理が不便なので,
最新版のimageにバージョンタグを付与してみる.
やり方としては最新のcommitにgit tagをつけてpushするだけ.

# masterブランチで作業
$ git checkout master
$ git pull origin master

# masterブランチの最新のcommitにvから始まるtagをつけてpushする
$ git tag -a v0.0.1 -m "hogehoge"
$ git push origin v0.0.1

するとtagのpushにトリガーされてworkflowが実行される.
今度はtagで指定したvからはじまる文字列がimageのタグとして付与されていることがわかる.
tagをつけたpush

再度Packagesの画面を開くと確かにgit tagで指定した0.0.1imageがpushされている.
タグがついてる

今回はコマンドラインでtagをつけたけど, UIからreleaseを作っても同様にimageのpushができる.
UIから作成したrelease

最後にimageの動作確認を行う.

# 動作確認
$ docker container run --rm ghcr.io/uzimihsr/echo:0.0.1
good morning! # CMDの変更が反映されている

やったぜ.
GitHub Actionsを使ってGitHub Container Registryimageをタグ付けしてpushできるようになった.

おわり

GitHubリポジトリ上のDockerfileからimageを作れるようになった.
GitHub Actionsを使うのも初めてだったので, 練習にもなってよかった.
Docker Hubが無料プランだといろいろ制限が厳しくなってきてるので2, (今のところは無料の)GitHub Container Registryに乗り換えていこうと思う.

おまけ

おもちゃで遊ぶねこ