Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

GitLab CI Error Guide: 'denied: requested access to the resource is denied' Container Registry Push 403

Fix GitLab CI's 'denied: requested access to the resource is denied' on docker push: log in with CI_REGISTRY credentials, push to CI_REGISTRY_IMAGE, and check roles.

  • #gitlab-cicd
  • #troubleshooting
  • #errors
  • #registry

Exact Error Message

A docker push to the GitLab Container Registry fails at the very end of the job:

The push refers to repository [registry.gitlab.com/acme/app]
5f70bf18a086: Preparing
denied: requested access to the resource is denied
ERROR: Job failed: exit code 1

The image built fine; the push was rejected. This is a 403-class denial from the registry, not a build error.

What the Error Means

The Container Registry refused to accept the push because the credentials presented are not authorized to write to that exact repository path. Either you never logged in, you logged in but lack push permission, or — very commonly — you are pushing to a path that isn’t under $CI_REGISTRY_IMAGE, so as far as the registry is concerned you are writing to a repository you don’t own.

This is distinct from a 401 Unauthorized (covered separately): a 401 means your credentials were not accepted at all, while denied: requested access to the resource is denied means you authenticated but are not allowed to push here.

Common Causes

  1. Not logged in with CI_REGISTRY credentials. No docker login ran, so Docker pushes anonymously.
  2. Pushing to a path that isn’t $CI_REGISTRY_IMAGE. Hard-coded registry.gitlab.com/someoneelse/... or a typo’d namespace.
  3. Registry disabled. Container Registry is turned off for the project (Settings > General > Visibility).
  4. Protected or immutable tag. A push to a protected tag (e.g. latest, release-*) from a job that doesn’t meet the protection rule.
  5. Cleanup / tag-immutability policy. An immutable-tag policy rejects overwriting an existing tag.
  6. Insufficient role. A Reporter can pull but cannot push; you need at least Developer.
  7. Expired or wrong token. A stale PAT/deploy token without write_registry scope.

How to Reproduce the Error

Build and push without logging in (or to the wrong path):

build-image:
  stage: build
  image: docker:27
  services:
    - docker:27-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA   # no docker login first
$ docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
The push refers to repository [registry.gitlab.com/acme/app]
denied: requested access to the resource is denied

Diagnostic Commands

Confirm the login, the target path, and the role from inside the job:

# What path are we pushing to? It MUST be under $CI_REGISTRY_IMAGE
echo "CI_REGISTRY=$CI_REGISTRY"
echo "CI_REGISTRY_IMAGE=$CI_REGISTRY_IMAGE"
echo "CI_REGISTRY_USER=$CI_REGISTRY_USER"   # safe; it is "gitlab-ci-token"

# Try the login explicitly and read the result
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
CI_REGISTRY=registry.gitlab.com
CI_REGISTRY_IMAGE=registry.gitlab.com/acme/app
CI_REGISTRY_USER=gitlab-ci-token
Login Succeeded

If Login Succeeded but the push is still denied, the problem is the path or your role, not authentication. Check Settings > General > Visibility (registry enabled?) and your project membership (Developer+?).

Step-by-Step Resolution

1. Log in with the built-in registry credentials

GitLab provides CI_REGISTRY_USER (gitlab-ci-token) and CI_REGISTRY_PASSWORD (the job token) automatically:

build-image:
  stage: build
  image: docker:27
  services:
    - docker:27-dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"

2. Always push under $CI_REGISTRY_IMAGE

The job token can only write to its own project’s registry path. Build every tag from $CI_REGISTRY_IMAGE:

script:
  - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" -t "$CI_REGISTRY_IMAGE:latest" .
  - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
  - docker push "$CI_REGISTRY_IMAGE:latest"

3. Confirm the registry is enabled and your role

  • Settings > General > Visibility, project features, permissions — ensure Container Registry is enabled.
  • Project members — pushing requires at least Developer; Reporter can only pull.

4. Handle protected / immutable tags

If only certain tags are protected, push from a job that meets the rule, or version your tag instead of overwriting an immutable one:

  - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"   # unique per release

5. Cross-project pushes need explicit credentials

To push to another project’s registry, the job token isn’t enough — use a deploy token with write_registry:

  - docker login -u "$DEPLOY_USER" -p "$DEPLOY_TOKEN" "$CI_REGISTRY"
  - docker push "registry.gitlab.com/acme/other-project:$CI_COMMIT_SHORT_SHA"

Prevention and Best Practices

  • Always run docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" in before_script — never push anonymously.
  • Build and tag exclusively from $CI_REGISTRY_IMAGE so you can never accidentally target a path the job token can’t write.
  • Push immutable, unique tags ($CI_COMMIT_SHORT_SHA, $CI_COMMIT_TAG) and reserve protected tags like latest/release-* for jobs that satisfy the protection rule.
  • Grant CI service accounts Developer (push) only where needed; keep deploy tokens scoped to write_registry and masked.
  • The free incident assistant can map a denied push log to login, path, or role fixes. More patterns live in the GitLab CI/CD guides.
  • 401 Unauthorized on docker login/pull — the authentication-layer twin. A 401 means the credential was rejected (bad login); this 403-style denied means you authenticated but lack push rights to the path. Fix 401 at login; fix denied at path/role.
  • You are not allowed to download code from this project — the repository-clone counterpart to a registry authorization denial.
  • x509: certificate signed by unknown authority — a TLS-trust failure that can also break docker login on self-hosted registries.

Frequently Asked Questions

Why does the build succeed but the push fail?

docker build is local to the runner and needs no credentials. docker push talks to the registry, which enforces authentication and authorization — so a missing login or insufficient role only surfaces at push time.

What’s the difference between this denied error and 401 Unauthorized?

401 Unauthorized means your credentials were not accepted (authentication). denied: requested access to the resource is denied means you are authenticated but not authorized to write to that repository path (authorization). Different layers, different fixes.

Why can my colleague push but I can’t?

Pushing to the registry requires at least the Developer role. A Reporter can pull images but receives denied on push. Check Project members.

Can I push to another project’s registry from CI?

Not with the default job token — it is scoped to its own project’s path. Use a deploy token (or Project Access Token) with write_registry scope and log in with it explicitly.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.