All Articles

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.

DevOpsBoysApr 19, 20265 min read
Share:Tweet

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:

yaml
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:latest

The 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)

  1. Create a PAT at GitHub → Settings → Developer Settings → Personal Access Tokens → Fine-grained tokens
  2. Grant scope: read:packages, write:packages, delete:packages
  3. Add it as a secret: Settings → Secrets → Actions → New secret → name it CR_PAT
yaml
- 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_TOKEN

Cause 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

  1. Add secrets in GitHub: Settings → Secrets → Actions
yaml
- 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:latest

Use 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

yaml
- 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:latest

Make sure the IAM user or role has these permissions:

json
{
  "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

yaml
- 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-app

Or create it with Terraform:

hcl
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

  1. Go to Organization → Settings → Packages
  2. Enable "Allow GitHub Actions to create and approve packages"
  3. 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

yaml
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

SymptomLikely CauseFix
permission_denied on GHCRMissing packages: write permissionAdd permissions block
unauthorized on GHCRWrong token or PAT scopeUse PAT with packages scope
incorrect username or password on Docker HubMissing/wrong secretsAdd DOCKERHUB_USERNAME + TOKEN secrets
no basic auth credentials on ECRNot using ECR login actionUse amazon-ecr-login action
repository not found on ECRECR repo doesn't existCreate repo before pushing
permission denied on org GHCROrg Actions access not enabledEnable in Org → Settings → Packages

Full Working Workflow (GHCR)

yaml
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=max

Summary

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.

Newsletter

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

Comments