Pull private images from the Cloider registry
To run an image from a private registry, Kubernetes needs credentials to pull it. For the Cloider container registry at registry.cloider.com, you create a pull-only registry token, store it in the cluster as a Secret of type kubernetes.io/dockerconfigjson, and reference that Secret as an imagePullSecret in your pods. This page shows the whole flow, from pushing an image to running it.
Before you begin
- A cluster in the
Readystatus. See Create a cluster. - A registry project with an image in it, for example
registry.cloider.com/acme/api:1.0. See Create a project and Push and pull images. - A role that can create registry tokens.
How it works
- A registry token is a username and secret that can log in to the registry. Give the cluster its own token with the Pull only permission, limited to the projects it needs, so a leaked cluster secret can't push or delete images.
- A Kubernetes Secret of type
kubernetes.io/dockerconfigjsonholds that login. Secrets are namespaced, so create it in every namespace whose pods pull private images. imagePullSecretsin a pod spec (or on the namespace's ServiceAccount) tells Kubernetes to use the Secret when it pulls.
Step 1: Create a pull-only registry token
- Dashboard
- CLI
- Terraform
- API
- MCP
- Open Registry in the sidebar and go to the Tokens tab.
- Click Create Token.
- In Name, type
staging-cluster. - In Projects, choose Only specific projects and pick
acme. - In Permission, choose Pull only.
- Choose when it Expires (the expiry can't be changed later).
- Click Create token.
The Token created dialog shows the Registry host, Username, Secret, a Docker login command and a ready Kubernetes pull secret command. Copy the pull secret command now: the secret isn't shown again. Then click I've saved the secret.
cloider registry token create --label staging-cluster --project acme --read-only
✓ Created registry token staging-cluster
Username: acme
Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Host: registry.cloider.com
Projects: acme
Permission: Pull
Expires: never
Docker:
echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' | docker login registry.cloider.com -u 'acme' --password-stdin
Kubernetes pull secret:
kubectl create secret docker-registry cloider-registry --docker-server=registry.cloider.com --docker-username='acme' --docker-password='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
! The secret is not shown again, keep it now.
Add --expires-days 365 to make the token expire after a year (0, the default, means never). See cloider registry token.
resource "cloider_registry" "acme" {
name = "acme"
}
# For the cluster to pull images with
resource "cloider_registry_token" "pull" {
label = "staging-cluster"
projects = [cloider_registry.acme.id]
read_only = true
}
The token's username and secret are used in step 2. See cloider_registry_token.
Registry tokens are created with POST /api/registry/tokens. See the Registry tokens API for the request and response, which includes the secret once.
Ask your agent:
Create a pull-only registry token called staging-cluster for the acme project.
It calls the create_registry_token tool:
{"label": "staging-cluster", "projects": ["acme"], "read_only": true}
The answer holds the username and the secret, which is never shown again. The secret ends up in the agent's context, so prefer the dashboard, CLI or Terraform for production credentials.
Step 2: Create the image pull secret in the cluster
Run the pull secret command from step 1, adding the namespace your pods run in with -n. Without -n, the Secret is created in the default namespace only.
- Dashboard
- CLI
- Terraform
- API
- MCP
Open the cluster's terminal (Kubernetes, then the cluster's name) and paste the command, with -n shop added:
kubectl create namespace shop --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret docker-registry cloider-registry -n shop \
--docker-server=registry.cloider.com \
--docker-username='acme' \
--docker-password='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
secret/cloider-registry created
With kubectl locally:
kubectl create secret docker-registry cloider-registry -n shop \
--docker-server=registry.cloider.com \
--docker-username='acme' \
--docker-password="$REGISTRY_SECRET"
Or without a kubeconfig, through the cluster's terminal. Passing the secret on stdin keeps it out of your shell history and the process list:
printf '%s' "$REGISTRY_SECRET" | cloider cluster exec -i staging -- sh -c \
'kubectl create secret docker-registry cloider-registry -n shop --docker-server=registry.cloider.com --docker-username=acme --docker-password="$(cat)"'
With the kubernetes provider configured from the cluster's kube_config:
resource "kubernetes_secret_v1" "registry" {
metadata {
name = "cloider-registry"
namespace = "shop"
}
type = "kubernetes.io/dockerconfigjson"
data = {
".dockerconfigjson" = jsonencode({
auths = {
(cloider_registry.acme.host) = {
username = cloider_registry_token.pull.username
password = cloider_registry_token.pull.secret
auth = base64encode("${cloider_registry_token.pull.username}:${cloider_registry_token.pull.secret}")
}
}
})
}
}
Run the kubectl create secret command through the cluster's exec endpoint, with the secret passed on stdin (base64 encoded in the request):
curl -N -X POST https://cloider.com/api/clusters/x8f2kq/exec \
-H "Authorization: Bearer $CLOIDER_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"command\": [\"sh\", \"-c\", \"kubectl create secret docker-registry cloider-registry -n shop --docker-server=registry.cloider.com --docker-username=acme --docker-password=\\\"\$(cat)\\\"\"], \"stdin\": \"$(printf '%s' "$REGISTRY_SECRET" | base64)\"}"
Ask your agent:
In the staging cluster, create an image pull secret called cloider-registry in the shop namespace for registry.cloider.com with the username acme and the secret in my REGISTRY_SECRET variable.
It calls cluster_exec, passing the secret on stdin:
{"cluster": "staging", "command": "kubectl create secret docker-registry cloider-registry -n shop --docker-server=registry.cloider.com --docker-username=acme --docker-password=\"$(cat)\"", "stdin": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
Check it:
kubectl get secret cloider-registry -n shop
NAME TYPE DATA AGE
cloider-registry kubernetes.io/dockerconfigjson 1 10s
Step 3: Use the secret in your pods
Reference the Secret in the pod template with imagePullSecrets:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: shop
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
imagePullSecrets:
- name: cloider-registry
containers:
- name: api
image: registry.cloider.com/acme/api:1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
kubectl apply -f api.yaml
kubectl rollout status deployment/api -n shop
Use the secret for every pod in a namespace
Instead of adding imagePullSecrets to each Deployment, attach the Secret to the namespace's default ServiceAccount. Every pod that uses that ServiceAccount then pulls with it:
kubectl patch serviceaccount default -n shop \
-p '{"imagePullSecrets": [{"name": "cloider-registry"}]}'
Pods created after the patch pick it up; restart existing Deployments with kubectl rollout restart if they failed to pull before.
Helm charts
Most charts take pull secrets as a value, often imagePullSecrets:
image:
repository: registry.cloider.com/acme/api
tag: "1.0"
imagePullSecrets:
- name: cloider-registry
Check the exact key with helm show values <chart>.
Verify
kubectl get pods -n shop -l app=api
kubectl describe pod -n shop -l app=api | grep -A3 "Events"
A successful pull shows Successfully pulled image "registry.cloider.com/acme/api:1.0" in the pod's events, and the pods are Running.
Rotate or revoke the token
Registry tokens can't be extended once created. To rotate, create a new token, update the Secret, and delete the old token:
kubectl create secret docker-registry cloider-registry -n shop \
--docker-server=registry.cloider.com \
--docker-username='acme' \
--docker-password="$NEW_REGISTRY_SECRET" \
--dry-run=client -o yaml | kubectl apply -f -
Running pods keep their already pulled images; the new Secret is used for the next pull. See Registry tokens for listing and deleting tokens.
Troubleshooting
ImagePullBackOff or ErrImagePull: run kubectl describe pod <pod> -n shop and read the events:
| Event message contains | Cause and fix |
|---|---|
unauthorized or authentication required | The Secret is missing, in another namespace, not referenced in imagePullSecrets, or the token expired or was deleted. Check kubectl get secret cloider-registry -n shop and the pod spec |
not found or manifest unknown | The repository or tag doesn't exist. Check cloider registry images acme api |
denied or forbidden | The token isn't allowed on that project. Edit the token's projects in Registry, Tokens, or with cloider registry token update |
The secret was created in default: the pull secret command from the dashboard and CLI doesn't include a namespace. Recreate it with -n <namespace>.
A tag was overwritten but pods run the old image: Kubernetes reuses an image already on the node when the tag is unchanged. Use a new tag for every release (recommended, see Immutable tags), or set imagePullPolicy: Always.
More cases are in Kubernetes troubleshooting and Registry troubleshooting.
Next steps
- Use the registry with Kubernetes in the Container Registry section.
- CI/CD for the registry, to build and push images on every commit.
- Deploy a workload.
Frequently asked questions
How do I pull private images from the Cloider registry in Kubernetes?
Create a pull-only registry token, store it in the cluster with kubectl create secret docker-registry cloider-registry -n <namespace> --docker-server=registry.cloider.com and the token's username and secret, then add imagePullSecrets with the name cloider-registry to your pod spec.
Why does my pod show ImagePullBackOff for a registry.cloider.com image?
Usually the pull secret is missing, in another namespace, not referenced in imagePullSecrets, or its token expired or cannot access the project. kubectl describe pod shows the exact reason in the pod events.
Should the cluster use a pull-only token?
Yes. Give each cluster its own token with the Pull only permission, limited to the projects it needs. A leaked cluster credential then cannot push or delete images.
Do I need a pull secret in every namespace?
Yes. Kubernetes Secrets are namespaced, so create the pull secret in each namespace whose pods pull private images, or attach it to each namespace's default ServiceAccount.