What's Actually Happening

Docker image push to Quay container registry fails. Image cannot be uploaded to the registry repository.

The Error You'll See

```bash $ docker push quay.io/myorg/myimage:v1.0

Error: denied: requested access to the resource is denied ```

Authentication error:

bash
Error: unauthorized: authentication required

Repository not found:

bash
Error: repository quay.io/myorg/myimage not found

Tag already exists:

bash
Error: tag v1.0 already exists

Quota exceeded:

bash
Error: quota exceeded: organization storage limit reached

Why This Happens

  1. 1.Not authenticated - No login to Quay registry
  2. 2.Wrong credentials - Invalid username or password/token
  3. 3.No push permission - User lacks write access to repository
  4. 4.Repository missing - Target repository doesn't exist
  5. 5.Organization quota - Storage quota exceeded
  6. 6.Tag protected - Tag marked as immutable/protected

Step 1: Authenticate with Quay

```bash # Login to Quay:

# With username/password: docker login quay.io -u myuser -p mypassword

# With token (recommended): docker login quay.io -u myuser -p $QUAY_TOKEN

# Token created in Quay UI: # User Settings -> Robot Accounts -> Create Robot Token # Or: Organization -> Robot Accounts

# Verify login: cat ~/.docker/config.json | jq '.auths.quay.io'

# Test authentication: docker pull quay.io/myorg/myimage:v1.0 # Should work if authenticated

# Logout and re-login: docker logout quay.io docker login quay.io

# Check credentials: docker-credential-desktop list # On Mac/Windows cat ~/.docker/config.json

# If using Docker credential helper: export DOCKER_CONFIG=~/.docker docker login quay.io ```

Step 2: Check Repository Permissions

```bash # Check repository exists: # In Quay UI: Organizations -> myorg -> Repositories

# Via API: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage"

# Check user permissions: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage?permissions=true"

# Check team permissions: # Organization -> Teams -> Team Permissions

# Required permissions: # - Write permission for push # - Admin permission for tag management

# Add user to team: # Organization -> Teams -> Add User

# Create robot account for CI: curl -X POST -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/organization/myorg/robots/myrobot" \ -H "Content-Type: application/json" \ -d '{"description":"CI push robot"}'

# Grant robot permissions: curl -X PUT -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage/permissions/myrobot" \ -H "Content-Type: application/json" \ -d '{"role":"write"}' ```

Step 3: Create Repository

```bash # Repository must exist before push (unless auto-create enabled)

# Create repository via API: curl -X POST -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository" \ -H "Content-Type: application/json" \ -d '{ "namespace":"myorg", "repository":"myimage", "visibility":"public", "description":"My application image" }'

# Create private repository: curl -X POST -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository" \ -H "Content-Type: application/json" \ -d '{ "namespace":"myorg", "repository":"myimage", "visibility":"private" }'

# Create via Quay UI: # Organizations -> myorg -> Create Repository

# Check repository exists: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage" | jq '.name'

# Enable auto-create for organization: # Organization Settings -> Allow auto-create repositories ```

Step 4: Check Organization Quota

```bash # Check organization quota: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/organization/myorg"

# View quota in UI: # Organization -> Settings -> Quota

# Check storage usage: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/organization/myorg/quota" | jq .

# If quota exceeded:

# Delete old images: curl -X DELETE -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage/tag/oldtag"

# Prune tags: curl -X POST -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage/tags/prune" \ -H "Content-Type: application/json" \ -d '{"keep_n_tags":5}'

# Request quota increase: # Organization Settings -> Quota -> Request Increase

# Or disable quota enforcement: # Organization Settings -> Quota -> Disable (admin only) ```

Step 5: Handle Tag Protection

```bash # Check tag protection rules: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage"

# Tag protection in UI: # Repository -> Settings -> Tag Expiration/Protection

# Protected tags cannot be overwritten: # If v1.0 is protected, cannot push new v1.0

# Remove tag protection: curl -X DELETE -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage/tag/v1.0"

# Push new tag: docker push quay.io/myorg/myimage:v1.1

# Disable tag immutability: # Repository Settings -> Tag Immutability -> Disable

# Or enable expiration: # Repository Settings -> Tag Expiration -> Set days ```

Step 6: Verify Image Name

```bash # Image name must match repository: # Repository: quay.io/myorg/myimage # Image: myorg/myimage or quay.io/myorg/myimage

# Tag image correctly: docker tag myimage:v1 quay.io/myorg/myimage:v1

# Check image name: docker images | grep quay.io

# Verify full registry path: docker inspect myimage:v1 | jq '.[0].RepoTags'

# Correct naming: # quay.io/<namespace>/<repository>:<tag> # quay.io/myorg/myapp:v1.0

# Wrong naming examples: # myorg/myimage:v1 # Missing quay.io # quay.io/myimage:v1 # Missing organization

# Fix by retagging: docker tag myimage:v1 quay.io/myorg/myimage:v1 docker push quay.io/myorg/myimage:v1 ```

Step 7: Check Network Connectivity

```bash # Test connectivity to Quay:

# HTTPS connectivity: curl -I https://quay.io/v2/

# Check API: curl https://quay.io/api/v1/discovery

# Test from Docker host: docker info | grep -i registry

# Check proxy settings: echo $HTTP_PROXY echo $HTTPS_PROXY echo $NO_PROXY

# Docker daemon proxy: # /etc/docker/daemon.json: { "proxies": { "default": { "httpProxy": "http://proxy:8080", "httpsProxy": "http://proxy:8080" } } }

# Test push: docker push quay.io/myorg/myimage:v1

# Check Docker daemon logs: journalctl -u docker | grep quay

# Firewall check: telnet quay.io 443 curl -v https://quay.io/v2/ ```

Step 8: Debug Push Process

```bash # Enable Docker debug: docker -D push quay.io/myorg/myimage:v1

# Check manifest: docker manifest inspect quay.io/myorg/myimage:v1

# View image layers: docker history quay.io/myorg/myimage:v1

# Check image size: docker images quay.io/myorg/myimage

# Large images may timeout: # Optimize image size: # - Use multi-stage builds # - Minimize layers # - Use .dockerignore

# Push layer by layer: docker push --disable-content-trust quay.io/myorg/myimage:v1

# Check registry v2 API: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/v2/myorg/myimage/manifests/v1"

# Check push status: curl -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage" ```

Step 9: Use Robot Account for CI

```bash # Robot account for automated push:

# Create robot account: curl -X POST -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/organization/myorg/robots" \ -H "Content-Type: application/json" \ -d '{"name":"ci-push","description":"CI robot"}'

# Docker login with robot: # Robot username format: <robot>+<organization> docker login quay.io -u ci-push+myorg -p <robot-token>

# Grant repository permissions: curl -X PUT -H "Authorization:Bearer $QUAY_TOKEN" \ "https://quay.io/api/v1/repository/myorg/myimage/permissions/ci-push+myorg" \ -H "Content-Type: application/json" \ -d '{"role":"write"}'

# In CI pipeline: docker login quay.io -u "$QUAY_ROBOT" -p "$QUAY_TOKEN" docker push quay.io/myorg/myimage:$CI_COMMIT_SHA ```

Step 10: Quay Registry Verification Script

```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-quay-push.sh #!/bin/bash

ORG=${1:-"myorg"} REPO=${2:-"myimage"} TOKEN=${3:-"$QUAY_TOKEN"}

echo "=== Authentication Test ===" curl -s -H "Authorization:Bearer $TOKEN" \ "https://quay.io/api/v1/user" | jq '.username' || echo "Not authenticated"

echo "" echo "=== Docker Login Status ===" cat ~/.docker/config.json | jq '.auths.quay.io' || echo "Not logged in"

echo "" echo "=== Repository Exists ===" curl -s -H "Authorization:Bearer $TOKEN" \ "https://quay.io/api/v1/repository/$ORG/$REPO" | jq '.name' || echo "Repository not found"

echo "" echo "=== Repository Permissions ===" curl -s -H "Authorization:Bearer $TOKEN" \ "https://quay.io/api/v1/repository/$ORG/$REPO?permissions=true" | jq '.permissions'

echo "" echo "=== Organization Quota ===" curl -s -H "Authorization:Bearer $TOKEN" \ "https://quay.io/api/v1/organization/$ORG/quota" | jq .

echo "" echo "=== Existing Tags ===" curl -s -H "Authorization:Bearer $TOKEN" \ "https://quay.io/api/v1/repository/$ORG/$REPO/tags" | jq '.tags | keys'

echo "" echo "=== Robot Accounts ===" curl -s -H "Authorization:Bearer $TOKEN" \ "https://quay.io/api/v1/organization/$ORG/robots" | jq '.robots[].name'

echo "" echo "=== Connectivity Test ===" curl -s -I https://quay.io/v2/ | head -5

echo "" echo "=== Recommendations ===" echo "1. Ensure Docker login to quay.io" echo "2. Verify repository exists" echo "3. Check user has write permission" echo "4. Use robot account for CI" echo "5. Check organization quota not exceeded" echo "6. Verify image name format" echo "7. Test network connectivity" EOF

chmod +x /usr/local/bin/check-quay-push.sh

# Usage: /usr/local/bin/check-quay-push.sh myorg myimage $QUAY_TOKEN ```

Quay Registry Push Checklist

CheckExpected
AuthenticationDocker logged in
Repository existsAPI returns repo info
Write permissionUser/robot has write role
Quota availableStorage not exceeded
Image namequay.io/org/repo:tag
Tag not protectedCan overwrite tag
ConnectivityCan reach quay.io

Verify the Fix

```bash # After fixing Quay registry push issues

# 1. Docker login docker login quay.io // Login succeeded

# 2. Check repository curl -H "Authorization:Bearer $TOKEN" https://quay.io/api/v1/repository/myorg/myimage // Repository exists

# 3. Push image docker push quay.io/myorg/myimage:v1 // Push complete

# 4. Verify in Quay UI // Repository shows new tag

# 5. Pull to verify docker pull quay.io/myorg/myimage:v1 // Image pulled successfully

# 6. Check via API curl -H "Authorization:Bearer $TOKEN" https://quay.io/api/v1/repository/myorg/myimage/tags // Tag v1 listed ```

  • [Fix Docker Registry Push Denied](/articles/fix-docker-registry-push-denied)
  • [Fix Docker Image Not Found](/articles/fix-docker-image-not-found)
  • [Fix Harbor Container Registry Push Failed](/articles/fix-harbor-container-registry-push-failed)