Quay Default Permissions for Robot Accounts
Configure Quay Registry default permissions to auto-grant read access to robot accounts on every new repository. API and team patterns.
π‘ Quick Answer: In Quay, go to Organization β Settings β Default Permissions β Create Default Permission. Select the robot account (e.g.,
orgname+ci_pull), set permission to Read, and choose βAnyoneβ as the creator scope. Every new repository created in the organization will automatically grant the robot read (pull) access β no manual per-repo configuration needed.
The Problem
In large organizations with hundreds of Quay repositories, manually granting pull access to robot accounts on each new repository is:
- Error-prone β developers forget to add the CI/CD robot account
- Time-consuming β each repo needs individual permission configuration
- Inconsistent β some repos accessible, others return
401 Unauthorized - Hard to audit β no single place to verify which repos the robot can access
Kubernetes ImagePullBackOff errors appear when a new repository is created but the robot account used in pull secrets hasnβt been granted access yet.
The Solution
Step 1: Create the Robot Account
# Via Quay API β create organization-level robot account
curl -X PUT "https://quay.example.com/api/v1/organization/myorg/robots/ci_pull" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"description": "CI/CD pull-only robot for Kubernetes clusters"}'
# Response includes the robot token
# {
# "name": "myorg+ci_pull",
# "token": "ABC123...",
# "description": "CI/CD pull-only robot for Kubernetes clusters"
# }Step 2: Configure Default Permissions (UI)
- Navigate to Organization β Settings β Default Permissions
- Click Create Default Permission
- Configure:
- Permission:
Read(pull only) - Applied to: Select robot account
myorg+ci_pull - Creator:
Anyone(applies regardless of who creates the repo)
- Permission:
- Click Create Default Permission
Step 3: Configure Default Permissions (API)
# Create default permission via API
# Grant read access to robot 'ci_pull' on all new repos
curl -X POST "https://quay.example.com/api/v1/organization/myorg/prototypes" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"role": "read",
"delegate": {
"kind": "user",
"name": "myorg+ci_pull"
}
}'
# Verify default permissions are set
curl -s "https://quay.example.com/api/v1/organization/myorg/prototypes" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" | jq '.prototypes[] | {
role: .role,
delegate: .delegate.name,
activating_user: .activating_user.name
}'Step 4: Apply to Existing Repositories
Default permissions only affect new repositories. For existing repos, batch-apply:
#!/bin/bash
# grant-robot-read-all-repos.sh
# Grants read access to robot account on all existing repos
ORG="myorg"
ROBOT="myorg+ci_pull"
QUAY_URL="https://quay.example.com"
# List all repositories in the organization
REPOS=$(curl -s "${QUAY_URL}/api/v1/repository?namespace=${ORG}" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" | \
jq -r '.repositories[].name')
for REPO in $REPOS; do
echo -n "Granting read on ${ORG}/${REPO}... "
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-X PUT "${QUAY_URL}/api/v1/repository/${ORG}/${REPO}/permissions/user/${ROBOT}" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"role": "read"}')
if [ "$HTTP_CODE" = "200" ]; then
echo "β
"
else
echo "β (HTTP $HTTP_CODE)"
fi
doneStep 5: Use in Kubernetes
# Create pull secret from robot token
kubectl create secret docker-registry quay-pull-secret \
--docker-server=quay.example.com \
--docker-username="myorg+ci_pull" \
--docker-password="${ROBOT_TOKEN}" \
--namespace=production
# Reference in Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
template:
spec:
imagePullSecrets:
- name: quay-pull-secret
containers:
- name: app
image: quay.example.com/myorg/myapp:v1.2.3Multiple Robot Accounts Pattern
# Create role-specific robots with different default permissions
# Read-only robot for production clusters
curl -X PUT "${QUAY_URL}/api/v1/organization/myorg/robots/prod_pull" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"description": "Production clusters β pull only"}'
curl -X POST "${QUAY_URL}/api/v1/organization/myorg/prototypes" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"role": "read", "delegate": {"kind": "user", "name": "myorg+prod_pull"}}'
# Write robot for CI/CD pipelines (push + pull)
curl -X PUT "${QUAY_URL}/api/v1/organization/myorg/robots/ci_push" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"description": "CI/CD pipelines β push and pull"}'
curl -X POST "${QUAY_URL}/api/v1/organization/myorg/prototypes" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"role": "write", "delegate": {"kind": "user", "name": "myorg+ci_push"}}'Scoped Default Permissions by Creator
# Only apply when a specific team/user creates repos
# Useful for team-specific robots
# When anyone on the "platform" team creates a repo,
# grant read to the platform robot
curl -X POST "${QUAY_URL}/api/v1/organization/myorg/prototypes" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"role": "read",
"delegate": {
"kind": "user",
"name": "myorg+platform_pull"
},
"activating_user": {
"name": "platform_admin"
}
}'graph TD
A[New Repository Created] --> B{Default Permissions Configured?}
B -->|No| C[Manual per-repo grants needed]
B -->|Yes| D[Auto-apply permissions]
D --> E[Robot: ci_pull β Read]
D --> F[Robot: ci_push β Write]
D --> G[Team: devops β Admin]
E --> H[K8s clusters can pull immediately]
F --> I[CI pipelines can push immediately]
C --> J[β ImagePullBackOff until fixed]
style H fill:#d1fae5,stroke:#10b981
style I fill:#d1fae5,stroke:#10b981
style J fill:#fee2e2,stroke:#ef4444Common Issues
Default Permissions Not Applied to Existing Repos
# Default permissions only affect NEW repositories
# Use the batch script above for existing repos
# Verify a specific repo's permissions
curl -s "${QUAY_URL}/api/v1/repository/myorg/myapp/permissions/user/" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" | jq '.permissions'Robot Account Missing from Permission Selector
# Robot must be created at the ORGANIZATION level, not repository level
# Verify it exists
curl -s "${QUAY_URL}/api/v1/organization/myorg/robots" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" | \
jq '.robots[] | .name'LDAP/SSO Users Not Triggering Default Permissions
# The "creator" in default permissions matches the Quay username
# For LDAP users, this is the LDAP uid, not email
# Verify with:
curl -s "${QUAY_URL}/api/v1/users/john_doe" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" | jq '.username'Permission Precedence
# If both default permission AND team permission exist:
# - Higher permission wins (write > read)
# - Explicit repo-level permissions override defaults
# - Robot with "read" default + "write" via team = "write" effectiveBest Practices
- One robot per purpose:
ci_pull(read),ci_push(write),scanner(read) β never share tokens across use cases - Always set default permissions: Prevents ImagePullBackOff on new repos
- Batch-apply to existing repos: Run the script after creating default permissions
- Rotate robot tokens: Use
rotate-quay-robot-tokensrecipe for periodic rotation - Audit permissions: Periodically list all prototypes and verify they match your security policy
- Use βAnyoneβ creator scope: Unless you have team-specific isolation requirements
- Prefer read over write: Only CI/CD push pipelines need write; everything else should be read-only
- Mirror to cluster pull secret: Use OpenShift global pull secret or namespace-level
imagePullSecrets
Key Takeaways
- Default permissions (prototypes) auto-grant access on new repositories only
- Use the Quay API
/organization/{org}/prototypesendpoint for automation - Separate robot accounts by role: read for clusters, write for CI/CD
- Batch-apply to existing repos with the provided script
- Combine with Kubernetes
imagePullSecretsor OpenShift global pull secret for seamless image pulling

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
