Quay Robot Accounts for Kubernetes Image Pulls
Create Quay robot accounts and configure Kubernetes imagePullSecrets for automated container image pulls from private registries.
π‘ Quick Answer: Create a Quay robot account with read-only repository permissions, then use its generated credentials to create a Kubernetes
docker-registrySecret and attach it as animagePullSecretto your ServiceAccount or Pod spec.
The Problem
You need to pull container images from a private Quay registry into your Kubernetes cluster. Using personal credentials is insecure β they have too many permissions, canβt be easily rotated, and get shared across teams. You need a service-level identity with minimal, auditable access.
Robot accounts solve this by providing:
- Scoped permissions β read-only access to specific repositories
- Separate credentials β no personal account exposure
- Easy rotation β regenerate tokens without affecting human users
- Audit trail β track which robot pulled what and when
The Solution
Step 1: Create a Robot Account in Quay
Navigate to your Quay organization and create a robot account:
Via Quay Web UI:
- Go to Organization β Robot Accounts β Create Robot Account
- Name it descriptively:
k8s_prod_puller - The full name becomes
orgname+k8s_prod_puller
Via Quay API:
# Create a robot account in your organization
curl -X PUT \
"https://quay.io/api/v1/organization/myorg/robots/k8s_prod_puller" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"description": "Kubernetes production cluster image puller"}'Response includes the robot credentials:
{
"name": "myorg+k8s_prod_puller",
"token": "ABC123...ROBOT_TOKEN...XYZ789",
"created": "2026-02-26T10:00:00Z",
"description": "Kubernetes production cluster image puller"
}Step 2: Grant Repository Permissions
Assign read-only access to specific repositories:
Via Quay Web UI:
- Go to Repository β Settings β User and Robot Permissions
- Add the robot account with Read permission
Via Quay API:
# Grant read permission on a specific repository
curl -X PUT \
"https://quay.io/api/v1/repository/myorg/my-app/permissions/user/myorg+k8s_prod_puller" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"role": "read"}'To grant access to multiple repositories:
# Grant read access to multiple repos
for repo in my-app backend-api worker-service; do
curl -X PUT \
"https://quay.io/api/v1/repository/myorg/${repo}/permissions/user/myorg+k8s_prod_puller" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"role": "read"}'
echo "Granted read access to myorg/${repo}"
doneStep 3: Create Kubernetes Secret
Use the robot account credentials to create a docker-registry Secret:
# Create the imagePullSecret
kubectl create secret docker-registry quay-pull-secret \
--docker-server=quay.io \
--docker-username="myorg+k8s_prod_puller" \
--docker-password="ABC123...ROBOT_TOKEN...XYZ789" \
--docker-email="robot@myorg.example.com" \
-n my-namespaceFor self-hosted Quay registries, replace quay.io with your registry URL:
kubectl create secret docker-registry quay-pull-secret \
--docker-server=quay.example.com \
--docker-username="myorg+k8s_prod_puller" \
--docker-password="${ROBOT_TOKEN}" \
--docker-email="robot@myorg.example.com" \
-n my-namespaceVerify the Secret was created:
kubectl get secret quay-pull-secret -n my-namespace -o jsonpath='{.type}'
# Output: kubernetes.io/dockerconfigjsonStep 4: Attach to ServiceAccount
The recommended approach is to add the Secret to the default ServiceAccount so all Pods in the namespace use it automatically:
# patch-sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: my-namespace
imagePullSecrets:
- name: quay-pull-secret# Patch the default ServiceAccount
kubectl patch serviceaccount default \
-n my-namespace \
-p '{"imagePullSecrets": [{"name": "quay-pull-secret"}]}'Step 5: Use in Pod Spec
Alternatively, reference the Secret directly in your Pod or Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: quay.io/myorg/my-app:v1.2.3
ports:
- containerPort: 8080
imagePullSecrets:
- name: quay-pull-secretStep 6: Verify the Pull Works
# Deploy and watch
kubectl apply -f deployment.yaml
kubectl rollout status deployment/my-app -n my-namespace
# Check events for pull success
kubectl describe pod -l app=my-app -n my-namespace | grep -A5 "Events:"You should see:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 10s kubelet Pulling image "quay.io/myorg/my-app:v1.2.3"
Normal Pulled 8s kubelet Successfully pulled image
Normal Created 8s kubelet Created container my-app
Normal Started 7s kubelet Started container my-appflowchart TD
A[Quay Robot Account] -->|Scoped Read Access| B[Quay Repository]
A -->|Token| C[K8s docker-registry Secret]
C -->|imagePullSecrets| D[ServiceAccount or Pod]
D -->|Authenticated Pull| B
B -->|Image| E[Running Container]Multi-Namespace Setup
For clusters with many namespaces, automate Secret creation:
#!/bin/bash
# create-pull-secrets.sh β Replicate Quay pull secret across namespaces
NAMESPACES=("production" "staging" "dev" "monitoring")
REGISTRY="quay.io"
ROBOT_USER="myorg+k8s_prod_puller"
ROBOT_TOKEN="${QUAY_ROBOT_TOKEN}"
for ns in "${NAMESPACES[@]}"; do
# Create namespace if it doesn't exist
kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f -
# Create or update the pull secret
kubectl create secret docker-registry quay-pull-secret \
--docker-server="$REGISTRY" \
--docker-username="$ROBOT_USER" \
--docker-password="$ROBOT_TOKEN" \
--docker-email="robot@myorg.example.com" \
-n "$ns" \
--dry-run=client -o yaml | kubectl apply -f -
# Patch default ServiceAccount
kubectl patch serviceaccount default \
-n "$ns" \
-p '{"imagePullSecrets": [{"name": "quay-pull-secret"}]}'
echo "β
Configured quay-pull-secret in namespace: $ns"
doneToken Rotation
Rotate robot tokens without downtime:
#!/bin/bash
# rotate-quay-token.sh β Regenerate robot token and update K8s secrets
ORG="myorg"
ROBOT="k8s_prod_puller"
# 1. Regenerate the token via Quay API
NEW_TOKEN=$(curl -s -X POST \
"https://quay.io/api/v1/organization/${ORG}/robots/${ROBOT}/regenerate" \
-H "Authorization: Bearer ${QUAY_API_TOKEN}" | jq -r '.token')
echo "New token generated for ${ORG}+${ROBOT}"
# 2. Update all Kubernetes secrets
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
if kubectl get secret quay-pull-secret -n "$ns" &>/dev/null; then
kubectl create secret docker-registry quay-pull-secret \
--docker-server=quay.io \
--docker-username="${ORG}+${ROBOT}" \
--docker-password="${NEW_TOKEN}" \
--docker-email="robot@${ORG}.example.com" \
-n "$ns" \
--dry-run=client -o yaml | kubectl apply -f -
echo "β
Updated secret in namespace: $ns"
fi
done
# 3. Rolling restart deployments to pick up new credentials
# (only needed if tokens are cached by container runtime)
echo "Secrets updated. Existing running containers are unaffected."
echo "New pulls will use the rotated token automatically."Common Issues
ImagePullBackOff β Authentication Required
Failed to pull image "quay.io/myorg/my-app:v1.2.3":
unauthorized: access to the requested resource is not authorizedFix: Check that the robot account has read permission on the repository and the Secret credentials match:
# Verify credentials work outside K8s
podman login quay.io \
--username "myorg+k8s_prod_puller" \
--password "${ROBOT_TOKEN}"
# Decode and inspect the K8s secret
kubectl get secret quay-pull-secret -n my-namespace \
-o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .Secret Not Found in Namespace
Error: secrets "quay-pull-secret" not foundFix: Kubernetes Secrets are namespace-scoped. You must create the Secret in every namespace where Pods need it.
Wrong Registry URL
If using a self-hosted Quay with a custom domain or port:
# Include the port if non-standard
kubectl create secret docker-registry quay-pull-secret \
--docker-server=quay.internal.example.com:8443 \
--docker-username="myorg+k8s_prod_puller" \
--docker-password="${ROBOT_TOKEN}" \
-n my-namespaceRobot Account Permission Levels
| Permission | Can Pull | Can Push | Can Admin |
|---|---|---|---|
| Read | β | β | β |
| Write | β | β | β |
| Admin | β | β | β |
β οΈ Always use Read for Kubernetes pull secrets. Write and Admin are only needed for CI/CD push pipelines.
Best Practices
- One robot per environment β Create separate robots for dev, staging, and production (
k8s_dev_puller,k8s_staging_puller,k8s_prod_puller) - Least privilege β Grant read-only access to only the repositories each cluster needs
- Rotate tokens quarterly β Use the rotation script above on a schedule
- Avoid org-level permissions β Grant per-repository access instead of org-wide read
- Use External Secrets Operator β For production, store the robot token in a vault and sync to K8s via ESO rather than manual
kubectl create secret - Audit regularly β Review robot account activity in Quayβs usage logs
- Name descriptively β
k8s_prod_pulleris better thanrobot1 - Never commit tokens β Use environment variables or sealed secrets for GitOps workflows
Key Takeaways
- Quay robot accounts provide service-level identities with scoped, auditable access β never use personal credentials in Kubernetes
- Create a
docker-registrySecret from the robot token and attach it viaimagePullSecretson ServiceAccounts or Pod specs - Automate multi-namespace setup and token rotation with simple shell scripts
- Always grant read-only repository access for pull-only use cases
- Combine with External Secrets Operator or Sealed Secrets for production-grade GitOps workflows

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
