Here’s the complete solution addressing all the configuration aspects:
ACR Service Connection Configuration:
First, ensure your service connection is properly configured with service principal authentication. Go to Project Settings → Service connections → Your ACR connection. The service principal must have AcrPull role on the registry. However, don’t rely on automatic credential exposure - you’ll need explicit mapping.
Secret Variable Scoping in Environments:
Define your variables at the environment level (Pipelines → Environments → Select environment → Add variable), but mark them as secret. The key issue is that secret variables require explicit reference in the pipeline context:
variables:
- group: acr-credentials # Link variable group if using
- name: acrServer
value: myregistry.azurecr.io
Environment-scoped secrets are only available within deployment jobs that reference that environment.
Kubernetes imagePullSecrets Setup:
You cannot directly substitute secret variables into YAML manifests. Instead, create the Kubernetes secret dynamically:
steps:
- task: KubernetesManifest@0
inputs:
action: 'createSecret'
secretType: 'dockerRegistry'
secretName: 'acr-secret'
dockerRegistryEndpoint: 'acr-service-connection'
namespace: 'production'
This task uses the service connection directly, bypassing variable substitution issues.
Variable Substitution Syntax:
If you must use variable substitution in manifests, use a two-step process:
- Create secret in script task:
kubectl create secret docker-registry acr-secret \
--docker-server=$(acrServer) \
--docker-username=$(ACR_USERNAME) \
--docker-password=$(ACR_PASSWORD)
- Reference the secret name (not credentials) in manifest:
imagePullSecrets:
- name: acr-secret
The dockerconfigjson field should never contain variable references - it’s a base64-encoded credential blob.
Service Principal RBAC Permissions:
Verify the service principal has:
- AcrPull role on ACR (minimum)
- Contributor role on AKS cluster (for secret creation)
- Reader role on resource group (for validation)
Use this command to verify:
az role assignment list --assignee <service-principal-id> \
--scope /subscriptions/<sub-id>/resourceGroups/<rg-name>
Complete Working Pipeline Pattern:
stages:
- stage: Deploy
jobs:
- deployment: DeployToAKS
environment: production
variables:
- group: acr-secrets
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@0
inputs:
action: 'createSecret'
secretType: 'dockerRegistry'
secretName: 'acr-auth'
dockerRegistryEndpoint: 'acr-connection'
- task: KubernetesManifest@0
inputs:
action: 'deploy'
manifests: 'k8s/deployment.yaml'
imagePullSecrets: 'acr-auth'
This approach eliminated our substitution failures. The key insight is that ACR authentication should use the KubernetesManifest task’s built-in secret creation, not manual variable substitution. The service connection handles credential management automatically, and environment scoping ensures the correct connection is used per stage.