Use Cloider registry images in Kubernetes and elsewhere
Kubernetes pulls images from the Cloider Container Registry like from any private registry: you store a pull-only registry token in the cluster as a Secret of type kubernetes.io/dockerconfigjson, and reference it in your pods as an imagePullSecret. This page explains the pattern with complete YAML, and shows how to do the same on other platforms.
For a step-by-step walkthrough on a Cloider cluster, from the token to a running pod, with every tool, see Pull private images in the Kubernetes section.
Before you begin
- A project with an image in it, for example
registry.cloider.com/acme/api:1.0. See Push and pull images. - A Kubernetes cluster and
kubectlpointed at it. For a Cloider cluster, see Connect with kubectl. - A registry token with the Pull only permission that covers the project.
If the project has Public pulls turned on, Kubernetes can pull its images without any Secret. Everything below is for private projects.
How it works
- A registry token (pull only) is the login.
- A Secret of type
kubernetes.io/dockerconfigjsonholds that login in the cluster. Secrets are namespaced, so it must be in the namespace of the pods that use it. imagePullSecretsin the pod spec, or on the namespace's ServiceAccount, tells the kubelet to use it when it pulls.
Step 1: Create a pull-only token
Create a token with Pull only permission, scoped to the projects the cluster runs. For example with the CLI:
cloider registry token create --label prod-cluster --project acme --read-only --expires-days 365
The output, and the dashboard's Token created dialog, include a ready Kubernetes pull secret command. Copy the secret now: it is shown once. See Registry tokens.
Step 2: Create the image pull secret
Run the pull secret command against your cluster, and add -n <namespace> for the namespace your pods run in. Without -n, kubectl creates the Secret in the default namespace, where pods in other namespaces can't use it.
kubectl create secret docker-registry cloider-registry \
--docker-server=registry.cloider.com \
--docker-username='acme' \
--docker-password="$CLOIDER_REGISTRY_SECRET" \
-n shop
secret/cloider-registry created
Check it:
kubectl get secret cloider-registry -n shop
NAME TYPE DATA AGE
cloider-registry kubernetes.io/dockerconfigjson 1 5s
As a YAML manifest
To keep the Secret with your other manifests (encrypted with a tool like Sealed Secrets or SOPS, never in plain text in git), write it as YAML. The .dockerconfigjson value is the base64 of Docker's config.json, which the dashboard's Download JSON button gives you:
base64 < cloider-registry-prod-cluster.json | tr -d '\n'
apiVersion: v1
kind: Secret
metadata:
name: cloider-registry
namespace: shop
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5jbG9pZGVyLmNvbSI6eyJ1c2VybmFtZSI6ImFjbWUiLCJwYXNzd29yZCI6Inh4eHgiLCJhdXRoIjoiWVdOdFpUcDRlSGg0In19fQ==
Or let kubectl write the YAML for you without creating anything:
kubectl create secret docker-registry cloider-registry \
--docker-server=registry.cloider.com \
--docker-username='acme' \
--docker-password="$CLOIDER_REGISTRY_SECRET" \
-n shop --dry-run=client -o yaml > cloider-registry-secret.yaml
In every namespace that needs it
A Secret is only visible in its own namespace. Create it in each namespace whose pods pull private images:
for ns in shop billing staging; do
kubectl create secret docker-registry cloider-registry \
--docker-server=registry.cloider.com \
--docker-username='acme' \
--docker-password="$CLOIDER_REGISTRY_SECRET" \
-n "$ns" --dry-run=client -o yaml | kubectl apply -f -
done
Step 3: Reference the secret in your pods
Add imagePullSecrets to the pod template. A complete Deployment and Service:
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
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: shop
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8080
kubectl apply -f api.yaml
kubectl rollout status deployment/api -n shop
deployment "api" successfully rolled out
The same imagePullSecrets block works in a Pod, StatefulSet, DaemonSet, Job or CronJob pod template.
Pin images by digest
A tag can be moved to another image; a digest can't. For deployments that must never change under you, pin the digest (from cloider registry images acme api -o wide or the Repositories tab):
image: registry.cloider.com/acme/api@sha256:4c7e3d1b9a8f6e5d4c3b2a1908f7e6d5c4b3a29180f7e6d5c4b3a2918f7e6d5c
Or protect release tags with immutability rules, so api:1.0 always means the same image.
Use the secret for every pod in a namespace
Instead of editing every Deployment, attach the Secret to the namespace's default ServiceAccount. Every pod that runs as that ServiceAccount then pulls with it:
kubectl patch serviceaccount default -n shop \
-p '{"imagePullSecrets": [{"name": "cloider-registry"}]}'
Or declaratively:
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: shop
imagePullSecrets:
- name: cloider-registry
Pods created after the change pick it up. Pods that run under their own ServiceAccount need it on that ServiceAccount too.
Helm charts
Most charts take pull secrets as a value, often named imagePullSecrets. Check the chart's values.yaml:
image:
repository: registry.cloider.com/acme/api
tag: "1.0"
imagePullSecrets:
- name: cloider-registry
helm upgrade --install api ./charts/api -n shop -f values.yaml
To install a chart that is itself stored in the registry as an OCI artifact:
echo "$CLOIDER_REGISTRY_SECRET" | helm registry login registry.cloider.com -u acme --password-stdin
helm install api oci://registry.cloider.com/acme/charts/api --version 0.1.0 -n shop
See Helm on Cloider.
Kustomize
Kustomize can generate the Secret from Docker's config.json, and set the image tag per environment:
namespace: shop
resources:
- api.yaml
secretGenerator:
- name: cloider-registry
type: kubernetes.io/dockerconfigjson
files:
- .dockerconfigjson=cloider-registry.json
generatorOptions:
disableNameSuffixHash: true
images:
- name: registry.cloider.com/acme/api
newTag: "1.1"
Keep cloider-registry.json out of git (add it to .gitignore) and create it in your pipeline.
Terraform
The cloider_registry_token resource and the kubernetes provider create the token and the pull secret together:
resource "cloider_registry" "acme" {
name = "acme"
}
resource "cloider_registry_token" "pull" {
label = "prod-cluster"
projects = [cloider_registry.acme.id]
read_only = true
}
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}")
}
}
})
}
}
See Kubernetes and Helm with Terraform.
Apps on Cloider App Engine
Apps don't need any of this. Cloider builds each app's image into your account's apps project and gives your apps access to it automatically. You don't create a registry token or a pull secret for an app. To run an image you built yourself, deploy it to a Kubernetes cluster.
Other platforms
The registry works anywhere that can log in to a Docker registry with a username and password.
k3s and RKE2 nodes
To let every pod on a k3s node pull from the registry without imagePullSecrets, configure the node's container runtime in /etc/rancher/k3s/registries.yaml (/etc/rancher/rke2/registries.yaml on RKE2) and restart the service:
configs:
"registry.cloider.com":
auth:
username: acme
password: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sudo systemctl restart k3s
Every workload on that node can then pull from the projects the token covers, so use a Pull only token.
Docker Swarm
Log in on the manager and pass the login to the nodes with --with-registry-auth:
echo "$CLOIDER_REGISTRY_SECRET" | docker login registry.cloider.com -u acme --password-stdin
docker stack deploy -c compose.yaml --with-registry-auth shop
HashiCorp Nomad
The Docker driver takes the login in the task's auth block:
task "api" {
driver = "docker"
config {
image = "registry.cloider.com/acme/api:1.0"
auth {
username = "acme"
password = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
In production, keep the secret out of the job file: Nomad can read it from Nomad Variables or Vault, or the Docker driver can use the node's own docker login through its plugin auth configuration.
A plain server with Docker
echo "$CLOIDER_REGISTRY_SECRET" | docker login registry.cloider.com -u acme --password-stdin
docker run -d --name api -p 8080:8080 registry.cloider.com/acme/api:1.0
Verify
kubectl get pods -n shop -l app=api
NAME READY STATUS RESTARTS AGE
api-6d9f7c8b5d-4kq2x 1/1 Running 0 40s
api-6d9f7c8b5d-p8wzn 1/1 Running 0 40s
The image's Last pulled time in the project's Repositories tab, and the repository's Pulls count, go up when the cluster pulls.
Troubleshooting
ImagePullBackOff or ErrImagePull means the kubelet couldn't pull the image. Read the pod's events:
kubectl describe pod -n shop -l app=api | grep -A8 Events
| Message in the events | 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 |
denied | The token doesn't cover this project. Edit the token's projects or use another token |
manifest unknown or not found | The image name or tag is wrong. Compare with cloider registry images acme api |
no match for platform in manifest | The image wasn't built for the node's CPU. Build for linux/amd64 or as a multi-platform image |
| Pod pulled before, fails after a restart | The token was deleted or expired. Create a new token and replace the Secret |
See Kubernetes troubleshooting and Registry troubleshooting.