Skip to main content

Pulling Images from a Private Container Registry

This guide shows how to configure your P6M cluster to pull container images from a private container registry. This is useful when your organization builds containers using CI/CD pipelines and pushes them to your own registry.

Overview​

The setup involves:

  1. Creating a registry access token with pull permissions
  2. Storing the credentials in a secret store
  3. Granting your cluster's External Secrets identity access to the secrets
  4. Deploying a ClusterExternalSecret that generates imagePullSecrets for your namespaces

Prerequisites​

  • kubectl access to your P6M cluster

Step 1: Create Registry Credentials​

Prerequisites​

  • Azure CLI installed and logged in
  • Access to your Azure Container Registry
Naming Convention

Throughout this guide, we use acr-p6m-shared as the name for the scope map, token, Key Vault, and Kubernetes resources. This creates a consistent, identifiable set of resources for your P6M cluster's registry access. You can customize this name to match your organization's naming conventions.

Set Defaults​

Look up your ACR's resource group and location:

az acr show --name <your-acr-name> --query "{ResourceGroup:resourceGroup, Location:location}" -o table

Set them as defaults for subsequent commands:

az configure --defaults group=<ResourceGroup> location=<Location>

Create a scope map​

This defines pull access to all repositories in your ACR:

az acr scope-map create \
--name acr-p6m-shared \
--registry <your-acr-name> \
--repository '*' content/read \
--description "Pull access to all repositories for P6M Clusters"

Create the token​

az acr token create \
--name acr-p6m-shared \
--registry <your-acr-name> \
--scope-map acr-p6m-shared

Save the username and password from the output. You'll need these in Step 2.


Step 2: Store Credentials in Secret Store​

Create the Key Vault​

az keyvault create --name acr-p6m-shared --enable-rbac-authorization false

Store the credentials​

Store each credential as a separate secret:

az keyvault secret set --vault-name acr-p6m-shared --name acr-username --value "<username>"
az keyvault secret set --vault-name acr-p6m-shared --name acr-password --value "<password>"
az keyvault secret set --vault-name acr-p6m-shared --name acr-host --value "<your-acr-name>.azurecr.io"

Step 3: Grant External Secrets Access​

Find your External Secrets service principal​

Each P6M cluster has a service principal for External Secrets. Find yours:

az ad sp list --all --query "[?ends_with(displayName, 'externalsecrets')].{Name:displayName, AppId:appId}" -o table

Look for {cluster-name}-externalsecrets (e.g., a1p-apps-dev-eastus-externalsecrets).

Grant secret read access​

az keyvault set-policy \
--name acr-p6m-shared \
--spn <AppId-from-above> \
--secret-permissions get

Step 4: Deploy Kubernetes Resources​

Apply the following resources to your cluster.

ClusterSecretStore​

This connects External Secrets to your Key Vault:

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: acr-p6m-shared
spec:
provider:
azurekv:
authType: WorkloadIdentity
vaultUrl: https://acr-p6m-shared.vault.azure.net/
serviceAccountRef:
name: external-secrets-platform
namespace: external-secrets

ClusterExternalSecret​

This generates the dockerconfigjson secret in any namespace with the label acr-pull-secret: enabled:

apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
name: acr-dockerconfig
spec:
namespaceSelector:
matchLabels:
acr-pull-secret: enabled
externalSecretSpec:
refreshInterval: 5m
secretStoreRef:
name: acr-p6m-shared
kind: ClusterSecretStore
target:
name: acr-dockerconfig
template:
type: kubernetes.io/dockerconfigjson
engineVersion: v2
data:
.dockerconfigjson: |
{"auths":{"{{ .host }}":{"username":"{{ .username }}","password":"{{ .password }}","auth":"{{ printf "%s:%s" .username .password | b64enc }}"}}}
data:
- secretKey: username
remoteRef:
key: acr-username
- secretKey: password
remoteRef:
key: acr-password
- secretKey: host
remoteRef:
key: acr-host

Step 5: Enable for Your Namespace​

Label any namespace that needs to pull images from your registry:

kubectl label namespace <your-namespace> acr-pull-secret=enabled

The acr-dockerconfig secret will be automatically created in that namespace.


Step 6: Use the Image Pull Secret​

Reference the secret in your pod or deployment:

spec:
containers:
- name: my-app
image: <your-registry>/my-image:tag
imagePullSecrets:
- name: acr-dockerconfig

Or configure the default service account to use it automatically:

kubectl patch serviceaccount default -n <your-namespace> \
-p '{"imagePullSecrets": [{"name": "acr-dockerconfig"}]}'

Testing the Setup​

To verify everything is working, push a test image and run it.

Push a test image to your ACR​

az acr import --name <your-acr-name> --source docker.io/library/hello-world:latest --image test/hello-world:latest

Run a one-off test pod​

kubectl run hello-world-test --rm -it --restart=Never \
-n <your-namespace> \
--image=<your-acr-name>.azurecr.io/test/hello-world:latest \
--overrides='{"spec":{"imagePullSecrets":[{"name":"acr-dockerconfig"}]}}'

If successful, you'll see the "Hello from Docker!" message and the pod will be automatically removed.


Troubleshooting​

Check ClusterSecretStore status​

kubectl get clustersecretstore acr-p6m-shared -o yaml

Look for status.conditions - it should show Ready: True.

Check ExternalSecret status​

kubectl get externalsecret acr-dockerconfig -n <namespace> -o yaml

Verify the secret was created​

kubectl get secret acr-dockerconfig -n <namespace>

Test the credentials​

kubectl get secret acr-dockerconfig -n <namespace> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .

Common issues​

IssueCauseSolution
ClusterSecretStore not readyService principal doesn't have Key Vault accessRe-run Step 3
Secret not created in namespaceNamespace missing labelRun kubectl label namespace <ns> acr-pull-secret=enabled
Image pull fails with "unauthorized"Incorrect credentials in Key VaultVerify secrets in Step 2 match the token from Step 1