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