GitHub Actions Docker Push: Permission Denied / Unauthorized Fix (2026)
Getting 'permission denied' or 'unauthorized: authentication required' when pushing Docker images in GitHub Actions? Here are all the causes and fixes.
You set up a GitHub Actions workflow to build and push a Docker image. The build step passes. Then the push step fails:
ERROR: denied: permission_denied
or
unauthorized: authentication required
or
Error response from daemon: Get "https://ghcr.io/v2/": unauthorized
This is one of the most common CI/CD failures. The fix depends on which registry you're pushing to. Let me walk through every cause.
Cause 1 — Missing permissions: packages: write (GHCR)
If you're pushing to GitHub Container Registry (ghcr.io), your workflow needs explicit write permission for packages.
Symptom
ERROR: denied: permission_denied
requesting: ghcr.io/your-org/your-image
Fix
Add permissions block to your workflow:
name: Build and Push
on:
push:
branches: [main]
permissions:
contents: read
packages: write # Required for GHCR push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}/app:latestThe GITHUB_TOKEN has packages write permission only when you explicitly declare it.
Cause 2 — Wrong GITHUB_TOKEN Scope for Private Repos
By default, GITHUB_TOKEN can push to GHCR only for the same repository. If your image belongs to an org or a different repo, the token scope is insufficient.
Fix — Use a Personal Access Token (PAT)
- Create a PAT at GitHub → Settings → Developer Settings → Personal Access Tokens → Fine-grained tokens
- Grant scope:
read:packages,write:packages,delete:packages - Add it as a secret:
Settings → Secrets → Actions → New secret→ name itCR_PAT
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.CR_PAT }} # PAT instead of GITHUB_TOKENCause 3 — Docker Hub Secret Not Set or Wrong Name
If you're pushing to Docker Hub and get unauthorized: incorrect username or password, the secret is missing or misnamed.
Fix
- Add secrets in GitHub:
Settings → Secrets → ActionsDOCKERHUB_USERNAME— your Docker Hub usernameDOCKERHUB_TOKEN— an Access Token (not your password) from hub.docker.com/settings/security
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/my-app:latestUse an Access Token, not your Docker Hub password. Tokens can be scoped and revoked independently.
Cause 4 — AWS ECR: no basic auth credentials
Pushing to Amazon ECR fails with:
no basic auth credentials
ECR requires a temporary auth token refreshed every 12 hours. You can't use static credentials like Docker Hub.
Fix — Use aws-actions/amazon-ecr-login
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-south-1
- name: Log in to ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push to ECR
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ steps.login-ecr.outputs.registry }}/my-app:latestMake sure the IAM user or role has these permissions:
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
}Cause 5 — ECR Repository Doesn't Exist Yet
ECR won't auto-create repositories on push. If the repo doesn't exist:
name unknown: The repository with name 'my-app' does not exist in the registry
Fix — Create the ECR repo first
- name: Create ECR repository if not exists
run: |
aws ecr describe-repositories --repository-names my-app 2>/dev/null || \
aws ecr create-repository --repository-name my-appOr create it with Terraform:
resource "aws_ecr_repository" "app" {
name = "my-app"
image_tag_mutability = "MUTABLE"
}Cause 6 — Org-Level Package Visibility (GHCR)
If pushing to an org's GHCR and getting permission denied even with a PAT:
The package might not be linked to the repository, or the org hasn't granted Actions access.
Fix
- Go to Organization → Settings → Packages
- Enable "Allow GitHub Actions to create and approve packages"
- On the package itself: Package Settings → Manage Actions access → Add repository → give Write access
Cause 7 — docker/login-action Not Running Before Push
If the login step is in a different job and you're not passing credentials between jobs, the build job has no auth context.
Fix — Login in the same job as push
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login # Must be in same job
uses: docker/login-action@v3
with: ...
- name: Build and push
uses: docker/build-push-action@v5
with: ...Each job runs in a fresh runner. Auth from one job doesn't carry to another.
Quick Diagnosis Checklist
| Symptom | Likely Cause | Fix |
|---|---|---|
permission_denied on GHCR | Missing packages: write permission | Add permissions block |
unauthorized on GHCR | Wrong token or PAT scope | Use PAT with packages scope |
incorrect username or password on Docker Hub | Missing/wrong secrets | Add DOCKERHUB_USERNAME + TOKEN secrets |
no basic auth credentials on ECR | Not using ECR login action | Use amazon-ecr-login action |
repository not found on ECR | ECR repo doesn't exist | Create repo before pushing |
permission denied on org GHCR | Org Actions access not enabled | Enable in Org → Settings → Packages |
Full Working Workflow (GHCR)
name: Build and Push to GHCR
on:
push:
branches: [main]
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxSummary
Most Docker push failures in GitHub Actions come down to three things: wrong token, missing permissions, or a registry-specific login flow. Match your registry to the fix above and you'll be unblocked in minutes.
For hands-on CI/CD practice with real pipelines, KodeKloud has excellent GitHub Actions labs that walk through these exact scenarios.
Want a managed registry without Docker Hub rate limits? DigitalOcean Container Registry gives you a private registry with $200 free credit for new accounts — no rate limiting, no pull limits.
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
Build a DevSecOps Pipeline from Scratch (2026 Project Walkthrough)
A complete end-to-end DevSecOps pipeline with SAST, container scanning, secrets detection, DAST, and supply chain security using open-source tools.
Build a Docker CI/CD Pipeline with GitHub Actions and AWS ECR (2026)
Step-by-step guide to building a production CI/CD pipeline that builds, scans, and pushes Docker images to AWS ECR using GitHub Actions.
GitHub Actions CI/CD Pipeline: Complete Tutorial for Docker & Kubernetes (2026)
Learn how to build a production-grade CI/CD pipeline using GitHub Actions. Covers Docker image builds, automated testing, secrets management, and Kubernetes deployments — with real workflow files.