Kubernetes

Mint a pull key

Create a project key scoped to pull only. A cluster never needs to push:

text
repositories:pull:acme/*

Narrow it to a single repository if the cluster only runs one service.

Create the secret

bash
kubectl create secret docker-registry lazer \
  --namespace production \
  --docker-server=lazer.sh \
  --docker-username=k8s \
  --docker-password="$LAZER_PULL_KEY"

Pull secrets are namespaced. Create one per namespace that runs your images.

Reference it

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
    name: api
    namespace: production
spec:
    template:
        spec:
            imagePullSecrets:
                - name: lazer
            containers:
                - name: api
                  image: lazer.sh/acme/api:2.4.1
                  imagePullPolicy: IfNotPresent

To avoid repeating it on every workload, attach the secret to the namespace's default service account instead:

bash
kubectl patch serviceaccount default \
  --namespace production \
  -p '{"imagePullSecrets":[{"name":"lazer"}]}'

Deploy by digest

Tags move. Digests do not. Deploying by digest means the image running in production is exactly the one CI built, even if someone retags:

yaml
image: lazer.sh/acme/api@sha256:9f3c...a71e

Get the digest in CI with crane digest lazer.sh/acme/api:$GIT_SHA and substitute it into the manifest.

Rotating the key

Update the secret in place; running pods are unaffected because they have already pulled:

bash
kubectl create secret docker-registry lazer \
  --namespace production \
  --docker-server=lazer.sh \
  --docker-username=k8s \
  --docker-password="$NEW_KEY" \
  --dry-run=client -o yaml | kubectl apply -f -

Then revoke the old key in the panel.

Troubleshooting

ImagePullBackOff with unauthorized usually means the secret is in the wrong namespace, or the key expired. Check with:

bash
kubectl get events --namespace production --field-selector reason=Failed

insufficient scope means the key is scoped to a different repository than the image reference names.