Secret variables are masked in logs, but masking is not magic. If your script transforms the secret, prints JSON with it embedded, or passes it to a tool that echoes config, the value can still leak indirectly.
Variables, Secrets & Key Vault
Learn how Azure DevOps handles configuration, secret values, variable groups, runtime expressions, and Azure Key Vault integration so your pipelines stay flexible without leaking credentials.
🧒 Simple Explanation (ELI5)
Think of a pipeline like a traveler going through different airports. The traveler needs labels like destination and seat number, and also private documents like a passport. Variables are the labels. Secrets are the private documents. Key Vault is the locked safe that stores the most sensitive documents until they are needed.
If you hardcode everything in the ticket, changing destinations becomes painful and exposing private data becomes easy. Good pipelines keep public settings reusable and sensitive settings protected.
🔧 Technical Explanation
Variable Types in Azure DevOps
| Type | Purpose | Example |
|---|---|---|
| Inline YAML variable | Simple reusable values in one pipeline | environment: dev |
| Pipeline UI variable | Configured in the pipeline editor | Toggle feature flag for one definition |
| Variable group | Shared values across many pipelines | Common ACR name, cluster name |
| Secret variable | Masked in logs and protected in UI | API keys, passwords, PATs |
| Key Vault linked variable group | Secrets pulled from Azure Key Vault | Database password from vault |
| Output variable | Value produced by one job and consumed later | Image tag generated during build |
Syntax You Must Know
variables:
environment: 'dev'
imageName: 'webapp'
steps:
- script: echo $(environment)
# Compile-time
${{ variables.imageName }}
# Runtime expression
$[ variables['Build.SourceBranch'] ]Macro syntax $(name) is what you use most often in task inputs and scripts. Template syntax ${{ }} is resolved earlier when Azure DevOps expands the YAML. Runtime expressions $[ ] are useful for conditional logic and dynamic assignment.
Variable Groups
Variable groups live under Pipelines → Library. They let multiple pipelines reuse the same values. This is useful for shared platform configuration like registry names, resource groups, namespaces, or public endpoints.
variables: - group: shared-platform-config - name: environment value: staging
Azure Key Vault Integration
For sensitive values, connect a variable group to Azure Key Vault. Azure DevOps fetches current secret values from the vault and injects them into the pipeline without storing the raw value in the YAML file.
- Create secrets in Azure Key Vault.
- Create or reuse an Azure service connection with permission to read vault secrets.
- Create a variable group and select Link secrets from an Azure Key Vault as variables.
- Select the vault and secret names to expose.
🛠️ Hands-on
Use Variable Groups in YAML
trigger:
- main
variables:
- group: aks-shared
- name: imageTag
value: '$(Build.BuildId)'
stages:
- stage: Build
jobs:
- job: BuildApp
pool:
vmImage: ubuntu-latest
steps:
- script: echo "Building $(imageRepository):$(imageTag)"
- script: echo "Target namespace is $(namespace)"Consume Key Vault Secrets in an Azure CLI Task
- task: AzureCLI@2
displayName: 'Call secure endpoint'
inputs:
azureSubscription: 'sc-platform-prod'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
curl -H "Authorization: Bearer $(api-token)" \
https://internal.example.com/healthPass a Secret to Helm Without Printing It
helm upgrade --install payments ./charts/payments \ --namespace production \ --set image.tag=$(Build.BuildId) \ --set-string secrets.connectionString=$(db-connection-string)
Prefer --set-string for secret-like values so Helm does not reinterpret characters. For larger secure config, use Kubernetes secrets or external secret operators instead of pushing too much through command line arguments.
🐛 Debugging Scenarios
Scenario 1: Secret Appears as Empty String
- Check the variable name for case and hyphen mismatches.
- Verify the variable group is linked to the pipeline.
- Confirm the pipeline is authorized to use the variable group.
Scenario 2: Key Vault Secrets Fail to Load
- Review the service connection identity permissions on the vault.
- Confirm the secret name still exists and is enabled.
- Check if the organization uses Key Vault access policies or RBAC and whether the identity has the correct model-specific permission.
Scenario 3: Secret Leaks in Logs
- Search for commands like
env,printenv, or verbose task output. - Check whether scripts echo JSON objects containing the secret.
- Reduce debug logging or sanitize outputs before printing.
Teams often move hardcoded passwords into secret variables but still write them to files and then publish those files as artifacts. Protect the whole flow, not just the source value.
📋 Interview Questions
Beginner
A shared collection of variables stored in Azure DevOps Library so multiple pipelines can reuse common configuration.
A secret variable is protected and masked in logs, while a normal variable is not intended for sensitive information.
To centralize secret storage, reduce duplication, improve rotation practices, and avoid storing raw secrets directly in pipeline definitions.
$(variableName) mean?It is macro syntax that Azure DevOps expands at runtime when the job executes.
Yes. You can reference multiple variable groups in one pipeline as long as the pipeline has permission to use them.
Intermediate
When secrets are shared across systems, need rotation, require central governance, or must be consumed by multiple workloads outside Azure DevOps.
They allow one job or stage to produce dynamic values, such as a computed image tag, that later jobs can consume without recalculating or hardcoding them.
Shared groups can become a hidden dependency, blur environment boundaries, and accidentally expose more variables than a pipeline really needs.
Use separate variable groups or templates per environment, keep names explicit, and apply least privilege so Dev pipelines cannot read Prod-only secrets.
Masking only knows the literal secret value. If the value is transformed, split, base64-encoded, or written to artifacts, it may no longer be masked and can leak.
Scenario-Based
I separate config by environment, use naming conventions, store sensitive values in Key Vault, validate critical values early in the pipeline, and keep configuration reviewable instead of spreading it across ad hoc UI settings.
I would check agent version, task behavior, environment injection, permission to variable groups, and whether a custom script clears environment variables before the task runs.
Store secrets centrally in Key Vault, support dual-valid credentials where possible, update consumers to accept the new secret, validate in lower environments, then retire the old value only after successful rollout.
It is possible but usually not ideal. I prefer managed service connections, federated identity, secure files, or workload identity because they reduce handling complexity and leakage risk.
Config tells the app where and how to run. Secrets prove identity or grant privileged access. Both may change by environment, but only secrets need strong confidentiality controls.
🌍 Real-World Usage
A real enterprise platform team usually standardizes shared non-secret variables in Library, keeps environment secrets in Key Vault, and exposes only the minimum set needed per pipeline. This supports safer CI/CD for build, ACR push, and AKS deploy flows without hardcoding anything in source control.
🧾 Summary
Variables make pipelines reusable. Secrets make them safe. Key Vault adds central control and better operational hygiene. Mature Azure DevOps delivery depends on separating public configuration from sensitive data and understanding exactly when each value is expanded and where it can leak.