Skip to main content

Create an imagePullSecret with kubectl

To pull images from a private registry, a Kubernetes cluster needs a Secret of type kubernetes.io/dockerconfigjson holding the registry login — commonly called an imagePullSecret. Without it, a Pod stays in ImagePullBackOff and the application never starts. This applies whether you're a developer deploying an app that uses a private image, a platform engineer preparing the Secret for several namespaces, or a CI/CD pipeline that re-creates it every time credentials rotate. By the end of this tutorial, you have a Secret in your namespace and a workload that successfully pulls a private image from Container Registry.

Prerequisites​

  • kubectl is connected to your cluster. Check with kubectl get nodes.
  • You have permission to create Secrets in the target namespace. Check with kubectl auth can-i create secret -n <namespace>.
  • You have Container Registry login credentials. On the FPT Portal, go to Container Registry > Get Token.

Use a Robot Account scoped to pull-only access for this instead of a personal account. See Create a robot account.

Create the Secret​

kubectl create secret docker-registry regcred \
--docker-server=registry.fptcloud.com \
--docker-username='robot$my-project+ci' \
--docker-password='Xy9!kP2$mQ' \
--namespace=my-app

A successful run returns:

secret/regcred created
warning

Three details here commonly produce a Secret that looks fine but doesn't authenticate.

  • Always wrap the password in single quotes ('...'). Characters like $, !, or & left unquoted get altered by the shell, so the Secret stores the wrong value even though the command reports success.
  • --docker-server must match only the host part at the start of the image name — no https:// prefix, no project name.
  • --namespace must be the same namespace the workload runs in. A Secret doesn't work across namespaces.
tip

Pass the username and password through environment variables instead of typing them directly into the command, and never commit a YAML file containing .dockerconfigjson to Git.

See imagePullSecret field reference for every flag kubectl create secret docker-registry accepts, and for the Secret naming rules.

Attach the Secret to a workload​

Add imagePullSecrets to the Pod template:

spec:
template:
spec:
imagePullSecrets:
- name: regcred
containers:
- name: api
image: registry.fptcloud.com/my-project/api:v1
kubectl apply -f deployment.yaml

If several workloads in the same namespace pull from the same registry, attach the Secret to the ServiceAccount instead of editing every manifest:

kubectl patch serviceaccount default -n my-app \
-p '{"imagePullSecrets":[{"name":"regcred"}]}'
note

Patching the ServiceAccount only affects Pods created after the patch. A Pod that's already running needs kubectl rollout restart to pick it up.

Verify the Secret works​

kubectl get pods -n my-app
StatusMeaningAction
ContainerCreatingStill pulling.Wait and check again.
RunningThe pull succeeded.Nothing else needed.
ErrImagePullThe first pull attempt failed.See Troubleshooting.
ImagePullBackOffKubernetes retried and gave up.See Troubleshooting.

To see the exact cause, check the Pod's Events:

kubectl describe pod <pod-name> -n my-app | tail -20

The Events section at the end distinguishes an authentication failure from an image that doesn't exist.

To inspect the credentials stored in the Secret:

kubectl get secret regcred -n my-app \
-o jsonpath='{.data.\.dockerconfigjson}' | base64 -d

A correct result contains an auths block with the registry address and a non-empty auth field, and the Pod reaches Running.

Create the Secret from a Docker config file​

Use this instead when the password contains characters that are hard to escape, or when you already ran docker login.

docker login registry.fptcloud.com

kubectl create secret docker-registry regcred \
--from-file=$HOME/.docker/config.json \
--namespace=my-app

Check the file before using this method:

cat ~/.docker/config.json

If the file contains "credsStore" instead of an "auths" block with an "auth" field, Docker is storing the credential in the OS keychain instead. The Secret created from it would be empty — use the kubectl create secret docker-registry command from the previous section instead.

Re-create or reuse the Secret across namespaces​

Re-running kubectl create secret on a Secret that already exists fails with AlreadyExists. In a script or CI/CD pipeline, pipe to kubectl apply instead so the command is safe to run repeatedly:

kubectl create secret docker-registry regcred \
--docker-server=registry.fptcloud.com \
--docker-username="$REG_USER" \
--docker-password="$REG_PASS" \
--namespace=my-app \
--dry-run=client -o yaml | kubectl apply -f -

A Secret doesn't work across namespaces, so repeat the same command for each namespace that needs it:

for ns in app-dev app-staging app-prod; do
kubectl create secret docker-registry regcred \
--docker-server=registry.fptcloud.com \
--docker-username="$REG_USER" \
--docker-password="$REG_PASS" \
--namespace="$ns" \
--dry-run=client -o yaml | kubectl apply -f -
done

Update the Secret after rotating credentials​

A Pod that's already running doesn't pick up a rotated credential on its own. After you re-create or update the Secret with new credentials, restart the workload so it reloads:

kubectl rollout restart deployment/api -n my-app

Next steps​