{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreierl4hzyt4xonqbwd2qtsrvkjqkigrdsvdgletfpjs5g3nyetpcvm",
"uri": "at://did:plc:g4wcucb6ko2frmko2x3lvgyi/app.bsky.feed.post/3miiht3dkkyi2"
},
"path": "/2026/04/01/dockerfile-pin/",
"publishedAt": "2026-04-02T05:26:42.388Z",
"site": "https://efcl.info",
"tags": [
"dockerfile-pin",
"azu/dockerfile-pin",
"trivyへのサプライチェーン攻撃",
"Can a Docker Hub tag have its content changed? - Docker Community Forums",
"トークンの制限が厳しくなっていたり",
"axiosのように問題が起きる",
"Immutable tags",
"@sha256:<digest>",
"Renovate",
"タグとdigestを同時に更新",
"pinact",
"dockpin",
"hadolint",
"hadolint#773",
"hadolint#1001",
"craneライブラリ",
"frizbee",
"aqua",
"GitHub Releases Immutability",
"suzuki-shunsuke/pinact",
"google/go-containerregistry",
"@sha256"
],
"textContent": "DockerfileやComposeファイルのイメージ参照に`@sha256:<digest>`を自動で追加するCLIツール dockerfile-pin を作りました。\n\n * GitHub: azu/dockerfile-pin\n\n\n\n## なぜ作ったか\n\ntrivyへのサプライチェーン攻撃などの事件を見ていると、次に狙われるのはDocker Hubかなと思ったのがきっかけです。 CIでDocker Hubへのpushをしているケースは多いので、そこに悪意あるコードが混入する事件は今後も起きるだろうと思っています。\n\nDockerイメージのタグ(例:`node:20`)はデフォルトで可変(mutable)です。同じタグ名で中身を上書きできるため、悪意ある第三者がレジストリへのアクセスを得た場合、既存タグに対して改竄されたイメージをpushできます。\n\n * Can a Docker Hub tag have its content changed? - Docker Community Forums\n\n\n\nDocker Hubなどのレジストリは安全とは限りません。 npmのようにトークンの制限が厳しくなっていたり、デフォルトでタグがimmutableな場所であっても、axiosのように問題が起きることはあります。 Docker HubにはImmutable tagsという機能がありますが、これはリポジトリオーナー側が設定するもので、イメージを利用する側がコントロールできるものではありません。\n\n@sha256:<digest>を付与することで、イメージの不変性を保証できます。digestはイメージのコンテンツハッシュなので、内容が異なればdigestも変わり、改竄を検知できます。npmのlockfileがパッケージのintegrityをハッシュで固定するのと同じ考え方です。\n\n\n # Before: タグのみ(可変)\n FROM node:20.11.1\n\n # After: タグ + digest(不変)\n FROM node:20.11.1@sha256:e06aae17c40c7a6b5296ca6f942a02e6737ae61bbbf3e2158624bb0f887991b5\n\n\nタグとdigestを両方残す形式にしておくと便利です。タグは人間が読むため、digestは不変性の保証のために残します。Renovateはこの形式でタグとdigestの両方を更新できます。Dependabotもdigestが既に付いている場合はタグとdigestを同時に更新できます。\n\nDockerfileでは明示的にSHA256 digestを指定しないとハッシュ固定ができません。これはGitHub Actionsの`uses:`をコミットSHAでpin留めしていないのと同じ状態で、サプライチェーン攻撃のリスクがあります。\n\nGitHub Actionsについてはpinactで自動化できますが、DockerfileのFROM行については同様のシンプルなツールがありませんでした。\n\n### 既存ツールが不十分だった\n\nDockerfileのSHA pinを補助する既存ツールとしてdockpinがありますが、2023年以降メンテナンスが停滞しています。\n\nまた、hadolintにはdigest pin強制ルールがなく(hadolint#773、2022年2月〜OPEN)、プラグイン機構もありません(hadolint#1001)。CIでdigestのpin漏れをチェックできるシンプルなlintツールが見当たりませんでした。\n\nそのため、pinactのDockerfile版をイメージして、craneライブラリ(Googleが管理、メンテナンスが活発)をベースに`dockerfile-pin`として自作しました。\n\n作った後に気づきましたが、frizbeeがGitHub ActionsとDockerの両方に対応した近いツールとして存在しています。dockerfile-pinはdigestの付与に加えて、CIで新しくdigestなしのイメージが入るのを防ぐ`check`コマンドがあるので、目的が少し異なる気がします。(おそらく似たことはできるはず?)\n\n## 使い方\n\n### インストール\n\n📝 主にCIとかで使いたい目的で作ったのでaquaなどのチェックサムをチェックしてインストールできる方法を推奨しています。\n\n\n # curl\n curl -sL \"https://github.com/azu/dockerfile-pin/releases/latest/download/dockerfile-pin_darwin_arm64.tar.gz\" | tar xz\n sudo mv dockerfile-pin /usr/local/bin/\n\n # aqua\n aqua generate -i azu/dockerfile-pin\n\n # Go\n go install github.com/azu/dockerfile-pin@latest\n\n\n### `run` コマンド: digestの追加\n\n`run --write`コマンドで、DockerfileやComposeファイルのイメージ参照にSHA256 digestを追加します。 デフォルトではDry-Runになっているので `--write` フラグを使うと実際にファイルを書き換えます。\n\n\n # ドライラン(プレビュー)\n dockerfile-pin run -f Dockerfile\n\n # 実際にファイルを書き換える\n dockerfile-pin run -f Dockerfile --write\n\n # globパターンで複数ファイルを対象にする\n dockerfile-pin run --glob '**/{Dockerfile,docker-compose.yml}' --write\n\n # 引数なしだと **/{Dockerfile,Dockerfile.*,docker-compose*.yml,docker-compose*.yaml,compose.yml,compose.yaml} を対象にします\n # ドライラン\n dockerfile-pin run\n # Docker関係のファイルを自動的に書き換える\n dockerfile-pin run --write\n\n\nたとえば、次のようにタグのみの指定にdigestが追加されます。\n\n**変換前:**\n\n\n FROM node:20.11.1\n FROM python:3.12 AS builder\n\n\n**変換後:**\n\n\n FROM node:20.11.1@sha256:e06aae17c40c7a6b5296ca6f942a02e6737ae61bbbf3e2158624bb0f887991b5\n FROM python:3.12@sha256:... AS builder\n\n\nすでにdigestが付いているイメージはスキップされます。`--update`オプションをつけると既存のdigestも更新します。\n\n### `check` コマンド: CIでのdigest検証\n\nCIで使うことを想定した`check`コマンドもあります。チェックは2段階です。\n\n 1. **構文チェック** : FROM行に`@sha256:`が含まれているか\n 2. **存在チェック** : 記載されたdigestがレジストリに実際に存在するか(HEADリクエストで検証)\n\n\n\n存在チェックがあることで、typoや削除済みdigestが`docker build`時まで発覚しないという問題を防げます。 HEADリクエストを使っているため、Docker Hubのpull rate limitを消費しません。\n\n\n # すべてのDockerfileをチェック(git ls-filesから自動検出)\n dockerfile-pin check\n\n # 構文チェックのみ(レジストリへのアクセスなし)\n dockerfile-pin check --syntax-only\n\n # JSON形式で出力\n dockerfile-pin check --format json\n\n # 特定のイメージを無視\n dockerfile-pin check --ignore-images scratch\n\n\n出力例は次のとおりです。\n\n\n FAIL Dockerfile:1 FROM node:20.11.1 missing digest\n OK Dockerfile:3 FROM python:3.12@sha256:abc123...\n SKIP Dockerfile:5 FROM scratch scratch image\n\n\n### 対応しているパターン\n\n**Dockerfile:**\n\n * `FROM image:tag` — digestを追加\n * `FROM image:tag AS stagename` — `AS`付きも対応\n * `FROM --platform=linux/amd64 image:tag` — `--platform`付きも対応\n * `ARG VERSION=1.0` + `FROM image:${VERSION}` — ARGにデフォルト値がある場合は展開して解決\n * `ARG BASE_IMAGE` + `FROM ${BASE_IMAGE}` — デフォルト値がない場合はwarningでスキップ\n * `FROM scratch` — スキップ\n * `FROM <stagename>` — マルチステージビルドの参照はスキップ\n * プライベートレジストリ(ghcr.io, GCR, ECRなど)にも対応\n\n\n\n**docker-compose.yml:**\n\n * `image: node:20` — digestを追加\n * `build:`ディレクティブがあるサービス — スキップ\n\n\n\n### CI/CDでの利用\n\nGitHub Actionsでの利用例です。curlでインストールする方法と、aquaを使う方法があります。aquaを使うと、dockerfile-pin自体のチェックサムを検証してインストールできます。\n\nなお、dockerfile-pinのリリースではGitHub Releases Immutabilityを有効にしています。\n\ncurlでインストールする場合:\n\n\n - run: |\n curl -sL \"https://github.com/azu/dockerfile-pin/releases/latest/download/dockerfile-pin_linux_amd64.tar.gz\" | tar xz\n sudo mv dockerfile-pin /usr/local/bin/\n - run: dockerfile-pin check\n\n\naqua経由で利用する場合は、`aqua.yaml`に `dockerfile-pin`を入れてインストールします。\n\n\n - uses: aquaproj/aqua-installer@d1fe50798dbadd4eb5b98957290ca175f6b4870f # v4.0.2\n with:\n aqua_version: v2.57.1\n - run: dockerfile-pin check\n\n\nCIで`dockerfile-pin check`を実行することで、digestが付いていないイメージをプルリクエスト時に検出できます。\n\n### Renovateとの併用\n\n初回のdigest付与は`dockerfile-pin run --write`で行い、その後の継続的な更新はRenovateに委譲します。\n\nRenovateの`docker:pinDigests`プリセットを有効にすると、`image:tag@sha256:digest`形式のdigestを自動更新するPRを生成してくれます。\n\n\n {\n \"extends\": [\"config:best-practices\"]\n }\n\n\n`config:best-practices`に`docker:pinDigests`が含まれています。digest更新のみ自動マージしたい場合は`default:automergeDigest`も利用できます。\n\n運用の流れとしては次のようになります。\n\n 1. `dockerfile-pin run --write`で既存ファイルにdigestを一括付与\n 2. CIに`dockerfile-pin check`を組み込み、digest未指定のFROM行がマージされないようにする\n 3. Renovateの`docker:pinDigests`で継続的にdigestを最新に保つ\n\n\n\n## まとめ\n\nDockerイメージのタグはデフォルトでmutableなので、タグだけの指定ではサプライチェーン攻撃のリスクがあります。 npmのlockfileやGitHub ActionsのSHA pinと同様に、Dockerfileでも`@sha256:<digest>`でイメージを固定した方が良いでしょう。\n\n既存ツール(dockpin、docker-lock)はメンテナンスが停滞しており、hadolintにもdigest pinのルールがありません。そのため、シンプルにpin付与とCIチェックを行うdockerfile-pinを作りました。\n\n * `dockerfile-pin run --write` で既存ファイルにdigestを一括追加\n * `dockerfile-pin check` でCIでdigestの付け忘れを検出\n * Renovateと組み合わせて継続的にdigestを最新に保つ\n\n\n\n## 参考\n\n * azu/dockerfile-pin\n * suzuki-shunsuke/pinact\n * google/go-containerregistry\n\n",
"title": "dockerfile-pin: DockerfileやComposeのイメージをSHA256でピン留めするCLIツールを作った",
"updatedAt": "2026-04-01T11:00:00.000Z"
}