GitHub Actions:ghcr.io への push が「denied: permission_denied: write_package」
レジストリはトークンを受け付け、そのうえでそのパッケージへの書き込みを断りました。ログインが通っても、パッケージのことは何もわかりません。確かめることは2つで、この順番です。ジョブのトークンに packages: write があるか。すでにあるパッケージに、このリポジトリが書き込めるか。
見え方
Login Succeeded! ... ERROR: failed to build: failed to solve: failed to push ghcr.io/octo-org/web:1.4.0: denied: permission_denied: write_package
これは docker/build-push-action です。docker push や helm push も同じ終わり方をします(mcp-context-forge #7074)。Organization のパッケージでは、代わりに denied: installation not allowed to Write organization package と出ることがあります。10月の報告では、こちらはトークンにそもそも書き込みが無いときが多く、write_package はパッケージがリポジトリを断ったときが多かったのですが、どちらの原因でもどちらも出るので、同じ順番で確かめます。
1. ジョブのトークンに packages: write が無い
- 既定が読み取りだけ。「個人アカウントに新しいリポジトリを作ると、既定では
GITHUB_TOKENはcontentsとpackagesの読み取りだけ」で、Organization も同じ設定にできます。ある Organization が切り替えたあと、permissionsの無いワークフローがすべて push できなくなりました(restate-cloud-tunnel-client #28)。 packagesを書いていないpermissionsは、それをnoneにします。books_downloader では、ワークフローのcontents: readがそうしました(#70)。- 再利用ワークフローは、呼び出し側が渡した以上の権限を持てないので、両方のファイルに要ります(restate-cloud-tunnel-client #28)。
- フォークからのプルリクエストのトークンは読み取りだけで、
permissionsでは上げられません。そこでは push せずにビルドだけします(shutterbase #128)。
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v7
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ghcr.io/octo-org/web:${{ github.sha }}
2. パッケージがもうあり、このリポジトリは書き込めない
packages: write がもうあるなら、問題はパッケージの側です。godot-mcp は両方そろい、GITHUB_TOKEN でログインし、push のところだけで断られました(godot-mcp #706)。Container registry のドキュメントには、同じ名前空間に先にパッケージを push していて、それをリポジトリにつないでいなければ、GITHUB_TOKEN には push の権限が無い、とあります。パッケージの設定のページで「Manage Actions access」とつながっているリポジトリを見ます。よくある経緯:
- 誰かが先に手元の PC から、または個人のトークンで push した。そうして作られたフォークのパッケージは、フォーク自身の
GITHUB_TOKENを断りました(armada-rp6 #59)。 - 別のリポジトリが先に出した。
ghcr.io/unipept/unipept-webはunipept/docker-imagesのもので、unipept/unipeptの v6.5.5 のリリースが失敗しました(unipept #1894)。分かれていたリポジトリを1つにまとめたモノレポでは、8つのイメージで一度に起きました(myota #53)。 - リポジトリの名前を変えたか作り直して、パッケージが古いほうのものとして残っている(iozy #14)。
- リポジトリを別のアカウントに移した。Container registry ではつながりが外れ、そのリポジトリの GitHub Actions のワークフローはパッケージに入れなくなる、とドキュメントにあります。
- Organization が新しいパッケージの「Inherit access from source repository」を切っていて、新しいパッケージがつながったリポジトリのワークフローに権限を渡さない。
抜け道
- リポジトリに権限を渡す。パッケージの設定のページの「Manage Actions access」で Add Repository を押し、Write の役割を付けます。パッケージの admin の権限が要ります。名前はそのままで、myota はこうしました。
- まだ誰のものでもない名前で push する。
GITHUB_TOKENでワークフローから出したパッケージは、そのリポジトリに自動でつながります。unipept と iozy はこれで直しました。古い名前を pull しているところも合わせて直します。 - 新しい名前でも最初の push が別の場所からになるなら、このリポジトリの URL を
org.opencontainers.image.sourceのラベルに入れます。パッケージがリポジトリの権限を引き継ぐのは、最初の公開の前にラベルがあるときだけです。すでにあるパッケージを設定のページからつないでも、引き継ぐを選ばない限り今の権限のままです。 write:packagesの個人のアクセストークンをシークレットにして使う。adcombi/ruby-ci-image はこうしました(#3)。その人が書き込めるパッケージならどれでも通りますが、このレジストリにはドキュメントがGITHUB_TOKENを勧めており、トークンは1人のもので、フォークのプルリクエストにはシークレットも届きません。
見分け方
- 「unauthorized: unauthenticated: User cannot be authenticated with the token provided」:トークンがそもそも受け付けられていません。空か、違うか、期限切れです。
- 「repository name must be lowercase」:イメージの名前に大文字があります。大文字の混じったオーナーで
${{ github.repository }}を使うとよく起きます。 git pushが「Permission to ... denied to github-actions[bot]」で断られるのは、同じ書き込み不足のcontents版です(git push と github-actions[bot])。
失敗したジョブを IntelliJ IDEA から見るなら
Pipeline Inbox for GitHub Actions Workflows は、Lesstep が作っている JetBrains IDE(2024.1 以降)のプラグインです。すべてのリポジトリの実行を、ジョブ・ステップと一緒に IDE のツールウィンドウに並べ、いまのブランチの実行はステータスバーに出ます。読むのは無料、失敗したジョブの再実行・キャンセル・ワークフローの実行・デプロイのレビューは Pro(月 US$1.50・30日の無料試用つき)です。
GitHub・GitHub Actions・GitHub Packages は GitHub, Inc. の商標、Docker は Docker, Inc. の商標、IntelliJ IDEA は JetBrains s.r.o. の商標です。このページは GitHub, Inc.・Docker, Inc.・JetBrains のいずれとも提携しておらず、承認も受けていません。
ほかに:「Resource not accessible by integration」 · git push の「Permission to ... denied to github-actions[bot]」 · 「No space left on device」 · IntelliJ IDEA で使える GitHub Actions のプラグインの比較 · English