Quay Default Permissions for Robot Accounts
Configure organizational default permissions in Quay Registry to automatically grant read access to robot accounts on every new repository. LDAP 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
