highIntermediateSymptom-first#ghcr#container#permissions#packages

GHCR push denied - packages:write permission missing

Docker push to ghcr.io fails with 'denied: permission_denied' or '403 Forbidden'

Likely source:availabilitysecurity

Focused incident mode — strips away everything but this runbook.

Symptoms

  • Docker push to ghcr.io fails with 'denied: permission_denied' or '403 Forbidden'
  • Workflow step 'docker/build-push-action' fails at the push phase
  • Error: 'insufficient permission: user does not have write access to package'

Evidence to grab

Collect these artifacts before you change anything — they prove (or rule out) each cause.

  • Docker login and push logs

    gh run view <RUN_ID> --log | grep -E 'login|push|denied'
  • Workflow permissions block

    cat .github/workflows/<file>.yml | grep -A10 'permissions'

Diagnostic flow

Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.

Guided decision tree is ready.

Steps with commands, expected output, and pass/fail branches.

Ranked root causes

Evidence checklist

  • Workflow uses actions: write but not packages: write
  • permissions block restricts to contents: read only

Confirm before fixing

cat .github/workflows/<file>.yml | grep -A10 'permissions'
READ

Read the permissions block

Fix

sed -i '/permissions:/a\ packages: write' .github/workflows/<file>.yml
CHANGE

Add packages: write to the permissions block

Resolution runbook

Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.

1
cat .github/workflows/<file>.yml | grep -A15 'permissions'
READ

Read current token permissions

2
gh run view <RUN_ID> --log-failed | grep -E 'denied|forbidden'
READ

Capture the exact error

3
sed -i '/permissions:/a\ packages: write' .github/workflows/<file>.yml
CHANGE

Add packages:write to the workflow permissions

4
gh workflow run <file>.yml --ref <BRANCH>
CHANGE

Re-run the workflow after the fix

Prevention

  • Always include packages: write in the permissions block of workflows that push container images.
  • Use GITHUB_TOKEN instead of PATs when possible to avoid secret rotation.
  • Set GHCR package visibility to private and scope permissions to specific repos.
  • Add a step that validates the token scope before attempting the push.

Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.