ngyuki/php-dev - Docker Hub に自分用の PHP の Docker イメージを置いています。これを新しい PHP のバージョンがリリースされたときに自動ビルドするために試行錯誤したメモです。
Docker Hub の Repository Links で自動ビルド → ダメ
最近になってから構成をガラッと変えているのですが、以前は次のような構成でした。
├── hooks/ │ ├── build │ └── post_push ├── check.php ├── Dockerfile ├── Makefile └── README.md
このリポジトリでは下記の PHP マイナーバージョンの最新版をビルドしています。
- 7.1
- 7.2
- 7.3
- 7.4
ですが Dockerfile はひとつだけ です。次のように ARG でビルドのベースになるイメージを指定できるようにしています。
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
docker build
のコマンドラインオプションでベースイメージが指定できます。
docker build . --build-arg BASE_IMAGE=php:7.4-alpine -t ngyuki/php-dev:7.4
Docker Hub でのビルドの実行時、リポジトリの中に hooks/build
が存在すればこれがデフォルトのビルドコマンドの代わりに使用されます(Advanced options for Autobuild and Autotest | Docker Documentation)。
これを使用して hooks/build
で次のように --build-arg
を指定します。
docker build . --pull --build-arg BASE_IMAGE=php:$DOCKER_TAG-alpine -f $DOCKERFILE_PATH -t $IMAGE_NAME
Docker Hub のビルドルールでは Docker Tag だけ異なる複数の設定を作っておきます。
これで master へプッシュすれば自動的に複数のバージョンがビルドされます。さらに REPOSITORY LINKS を有効にして、ベースイメージが更新されたときに自動でビルドが実行されるようにします。
と思っていたのですが吹き出しの部分をよく見ると、アンオフィシャルイメージでしか効果ない、と書いてるじゃないですか・・・なぜか長いこと気づいていませんでした。
そもそものところ、ベースイメージを ARG 指定するような方法だと静的に Dockerfile が解釈できないため、たとえアンオフィシャルイメージを使っていたとしても REPOSITORY LINKS は効かないのでは・・?(未確認)
Dependabot を使う → ダメ
Docker Hub の REPOSITORY LINKS で自動的にビルドするのは無理そうなので、新しいバージョンがリリースされたときに自動的に Pull Request を出してくれる系のサービスを使うことにします。
ざっとググったところ Dependabot というのが有名なようです。これ GitHub に買収 されていて、GitHub にネイティブに統合 されているので、Github の Insights > Dependency graph > Dependabot で状態が閲覧できたりするようです。
それなら Dependabot 一択かな・・と思ったのですが、GitHub ネイティブ統合版だと Pull Request を作成するまではやってくれるものの自動マージはできないようです。automerge のような設定がありません。
Dependabot Preview と呼ばれる GitHub ネイティブ統合前のものなら 自動マージをサポート しているのでこちらを使おうとしたのですが、7.3.21
-> 7.3.22
のようにパッチバージョンだけを更新させる方法がわかりませんでした。
設定の automerged_updates には semver:patch
なる条件があるのですが allowed_updates で同じものは指定できません。
ので 7.3.21
-> 7.4.9
のようにマイナーバージョンが更新されてしまいます。複数のマイナーバージョンをビルドしたいのでこれでは都合が悪いです。
Renovate を使う
Dependabot だとダメそうなので Renovate を使うことにしました。リポジトリの構成は次のようになりました。
├── 7.1/ │ ├── Dockerfile │ └── hooks -> ../hooks/ ├── 7.2/ │ ├── Dockerfile │ └── hooks -> ../hooks/ ├── 7.3/ │ ├── Dockerfile │ └── hooks -> ../hooks/ ├── 7.4/ │ ├── Dockerfile │ └── hooks -> ../hooks/ ├── hooks/ │ ├── build │ ├── post_push │ └── test ├── Dockerfile.in ├── Makefile ├── README.md ├── renovate.json ├── check.php └── latest -> 7.4
Dockerfile に具体的なバージョン番号を書く必要があるので Dockerfile はバージョンごとに作成します。中身はほとんど同じなので Dockerfile.in
をテンプレートとして作成しています。
Docker Hub のビルドルールは次の通り。バージョンごとにビルドコンテキストを指定しています。
Renovate で自動マージするためには CI で OK になる必要があります。ので Docker Hub の Autotest を有効にしています。
Autotest は docker-compose.test.yml
で sut
というサービスを定義しておくと実行されます。
# docker-compose.test.yml sut: build: . command: run_tests.sh
がしかし docker-compose.test.yml
をビルドコンテキストごと、つまりバージョンごとに配置するのも煩雑な気がしたし、既に hooks/
はすべてのビルドコンテキストでシンボリックリンクで共有するようにしているので、それならばと思ってテストもカスタムフックスクリプト hooks/test
で実行することにしました。
#!/bin/bash echo Running custom test set -eux docker run --rm -i "$IMAGE_NAME" -d zend_extension=xdebug.so -d opcache.enable_cli=1 < ../check.php
また、最新版に latest
タグが付くように hooks/post_push
フックを使っています。リポジトリの latest
シンボリックリンクが最新版のディレクトリを指すようにしているので、スクリプトでビルドコンテキスト(カレントディレクトリ)と latest
シンボリックリンクの位置が同じかチェックして、同じなら latest
タグもプッシュします。
#!/bin/bash echo Running post push set -eux if [ "$(readlink -f .)" == "$(readlink -f ../latest)" ]; then docker tag "$IMAGE_NAME" "$DOCKER_REPO:latest" docker push "$DOCKER_REPO:latest" fi
renovate.json
は Renovate の設定です。separateMinorPatch: true
にすればマイナーバージョンとパッチバージョンで別々に PR が出ます(7.3.21 -> 7.3.22
と 7.3.21 -> 7.4.9
が別になる、という意味)。さらに packageRules
でメジャーバージョンとマイナーバージョンの更新を無効、パッチバージョンの更新だけ有効にします。
// renovate.json { "extends": [ "config:base" ], "timezone": "Asia/Tokyo", "labels": ["renovate"], "enabledManagers": ["dockerfile"], "rebaseWhen": "behind-base-branch", "prHourlyLimit": 10, "prConcurrentLimit": 10, // マイナーバージョンとパッチバージョンの更新を分ける "separateMinorPatch": true, "packageRules": [ { // メジャーバージョンとマイナーバージョンの更新は無効 "datasources": ["docker"], "updateTypes": ["major", "minor"], "enabled": false }, { // パッチバージョンの更新を有効にする "datasources": ["docker"], "updateTypes": ["patch"], "enabled": true, // 1つのグループ(PR)にまとめる "groupName": "php", // 自動マージを有効にする "automerge": true } ] }
これで次のような PR が自動で作成&テストが通れば自動でマージされて Docker Hub のイメージが更新されます。
これは便利!
さいごに
よく考えたらもっとシンプルに次のような構成でも良かったかも・・
├── hooks/ │ ├── build │ ├── post_push │ └── test ├── Dockerfile.7.1 ├── Dockerfile.7.2 ├── Dockerfile.7.3 ├── Dockerfile.7.4 ├── Dockerfile.in ├── Makefile ├── README.md ├── renovate.json ├── check.php └── latest -> Dockerfile.7.4
renovate.json
の filematch で Dockerfile のパターンは指定出来るし。うーん、なにか理由があってビルドコンテキストを分けたような気がするのだけど・・なんだったかな?
また、今なら Docker Hub でカスタムフックスクリプトを使っていろいろやるぐらいなら Github Actions でビルド&プッシュする方が簡単かもしれません。Docker Hub だとなかなか確認に時間がかかります。