In an Azure Pipelines YAML file, what does referencing a variable group under variables: give the run, and what changes when that group is linked to Azure Key Vault?
answer
- shared bag of values, stored outside the pipeline
- the list form is contagious
- fetched at run time, not copied
- secrets, but not keys or certificates
- not in the environment unless you map it
basics
~20 sReferencing a variable group pulls every variable stored in that Library group into the pipeline's variable scope. Linking the group to Azure Key Vault means the values are fetched from the vault at run time through a service connection instead of being stored in Azure DevOps.
solid answer
~60 sA variable group lives in the Azure DevOps **Library** and is shared across pipelines; a YAML pipeline consumes it with `variables: - group: my-group`, which makes every variable in the group available at the scope where you declared it. Two mechanical details bite people. First, once you use the `- group:` list form you must write *all* your variables in list form (`- name: x` / `value: y`) — you cannot mix it with the plain mapping style. Second, the group has to be authorised for the pipeline; the first run prompts for that permission. Linking the group to **Azure Key Vault** changes the storage, not the syntax: you attach an Azure Resource Manager service connection and select which secrets to surface, and at run time Azure DevOps fetches the current values from the vault. Only Key Vault *secrets* are supported — not keys or certificates — and every value that arrives is treated as a secret variable, which means it is masked in logs and is **not** placed in the environment automatically.
code
yaml · 14 linesvariables:
- group: prod-secrets
- name: buildConfiguration
value: Release
steps:
- bash: |
set -euo pipefail
curl -sS -H "Authorization: Bearer $API_KEY" \
"https://example.internal/deploy?config=$BUILD_CONFIG"
displayName: Call deploy API
env:
API_KEY: $(apiKey)
BUILD_CONFIG: $(buildConfiguration)go deeper
Know that a variable group is shared configuration stored in the Library and that - group: pulls it into a pipeline. Say that the group must be authorised for the pipeline before the first run succeeds.
Explain the run-time fetch through the service connection, the secrets-only limitation, and why a secret referenced as a bare environment variable in a script comes back empty. Show the env: mapping that fixes it.
Show judgment about scoping: declare production groups at the stage or job that needs them, not at the pipeline root, and diagnose vault permission and service-principal expiry as a first-class failure. Treat masking as a backstop, not a control.
Own the decision of where credentials live at all — vault-backed groups versus federated identity with no stored secret — and the operational cost of every service connection you add: an identity to rotate, audit and revoke across every pipeline that depends on it.
## What a variable group is A variable group is a named bag of key/value pairs stored in the Azure DevOps project's **Library**, outside any single pipeline. Its point is sharing: one `prod-config` group can feed a dozen pipelines, and rotating a value there changes every consumer at once. A YAML pipeline pulls one in by name: ```yaml variables: - group: prod-config - name: buildConfiguration value: Release ``` Everything in `prod-config` is now readable as an ordinary pipeline variable — `$(apiEndpoint)`, and so on. ## Two mechanical gotchas **The list form is contagious.** Azure accepts two shapes under `variables:` — a simple mapping (`buildConfiguration: Release`) and a sequence of typed entries. `- group:` only exists in the sequence form, so the moment you add a group, every other variable in that block has to be rewritten as `- name: … / value: …`. A half-converted block is a YAML parse error, and it is the first thing people hit. **Authorisation is separate from reference.** Naming a group does not grant access to it. The group carries its own pipeline permissions, and the first run of a pipeline that references an unauthorised group waits for someone to approve it. This is a feature, not friction: it prevents any pipeline in the project from silently helping itself to production credentials by typing the right name. ## Scope `variables:` can be declared at pipeline, stage or job level, and a group declared at stage level is visible only inside that stage. That is the natural way to keep a production group out of the reach of a build stage that has no business reading it. ## Linking to Key Vault A group can instead be marked as linked to an **Azure Key Vault**. You choose an Azure Resource Manager service connection, pick the vault, and select which secret names to expose. The YAML does not change at all — it is still `- group: prod-secrets`. What changes is where the value comes from: - Values are **fetched from the vault at run time**, at the start of the run, rather than being copied into Azure DevOps. Rotating the secret in the vault means the next run picks up the new value with no edit anywhere. - The service connection's identity needs **Get** and **List** permissions on the vault's secrets — `List` to enumerate the names in the UI, `Get` to read values during the run. A group that worked yesterday and returns nothing today is very often an expired service-principal credential or a changed vault access policy. - **Only secrets are supported.** Key Vault also stores keys and certificates; a linked variable group cannot surface those. - Every value arrives as a **secret variable**. ## The secret-variable behaviour that surprises people Secret variables are handled differently from ordinary ones, and the difference causes a specific, very common bug: ```yaml steps: - bash: | echo "len=${#API_KEY}" # prints 0 — not in the environment displayName: Broken - bash: | echo "len=${#API_KEY}" # correct env: API_KEY: $(apiKey) displayName: Working ``` Ordinary pipeline variables are mapped into each task's process environment automatically. **Secret variables are not** — precisely so that a rogue task or a process dump does not sweep them up for free. To use one inside a script you map it explicitly with `env:`, or you reference `$(apiKey)` directly in a task input, where the agent substitutes it. The failure mode is quiet: the script sees an empty string, and you get an authentication error from a downstream service rather than a message about the variable. Secret values are also **masked in the logs** — Azure replaces occurrences with `***`. Treat that as a backstop only: a script that base64-encodes or otherwise transforms the value before printing it defeats the masking entirely, because the agent is matching on the literal string. One further constraint follows from the timing: secrets exist only once the run starts, so a secret can never be read by a compile-time `${{ }}` expression. Anything that must *shape* the pipeline has to come from a parameter, not a secret. ## Choosing between the two Plain variable groups are fine for non-sensitive shared configuration — region names, feature toggles, image tags. Key Vault linkage earns its extra moving part when the values are real credentials: the vault owns rotation, access history and its own access policies, and Azure DevOps holds only a reference. The cost is one more identity to keep alive and one more failure mode (vault permissions) between a green pipeline and a red one.
- A bash step references a secret from a Key Vault-linked group as $API_KEY and gets an empty string. Why?Because secret variables are deliberately not mapped into a task's process environment. Ordinary variables are; secrets are withheld so a compromised or careless task cannot enumerate them. Map it explicitly with an `env:` block on the step, or reference `$(apiKey)` in a task input where the agent performs the substitution.
- Which Key Vault permissions does the service connection's identity need?Get and List on secrets. `List` lets the Library UI enumerate secret names when you configure the group; `Get` lets the run read the values. Losing either — through an expired service-principal credential or a changed access policy — makes the group resolve to nothing, usually surfacing as an auth failure downstream rather than an obvious pipeline error.
- Can you reference a variable-group secret from a ${{ }} expression?No. Secrets are fetched when the run starts, whereas `${{ }}` is evaluated during template expansion beforehand — there is nothing to read. Anything that must change the pipeline's structure has to come from a parameter. A secret can only influence behaviour at runtime, through `$( )` substitution or a `condition:`.
saying these in an interview costs you the question
- Thinks Key Vault values are copied into Azure DevOps once
- Expects secret variables in the environment automatically
- Believes log masking makes a secret safe to print
- Says a linked group can expose certificates and keys
- Mixes mapping-style variables with a - group: entry