Skip to main content

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 kubectl pointed at it. For a Cloider cluster, see Connect with kubectl.
  • A registry token with the Pull only permission that covers the project.
Public projects need no secret

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​

  1. A registry token (pull only) is the login.
  2. A Secret of type kubernetes.io/dockerconfigjson holds that login in the cluster. Secrets are namespaced, so it must be in the namespace of the pods that use it.
  3. imagePullSecrets in 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
Output
secret/cloider-registry created

Check it:

kubectl get secret cloider-registry -n shop
Output
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'
cloider-registry-secret.yaml
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:

api.yaml
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
Output
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:

serviceaccount.yaml
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:

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:

kustomization.yaml
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:

main.tf
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:

/etc/rancher/k3s/registries.yaml
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:

api.nomad.hcl
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
Output
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 eventsCause and fix
unauthorized or authentication requiredThe 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
deniedThe token doesn't cover this project. Edit the token's projects or use another token
manifest unknown or not foundThe image name or tag is wrong. Compare with cloider registry images acme api
no match for platform in manifestThe 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 restartThe token was deleted or expired. Create a new token and replace the Secret

See Kubernetes troubleshooting and Registry troubleshooting.

Next steps​