Skip to content

Azure App Configuration

Key Points

  • Azure App Configuration = centralized config + feature flags. Replaces per-app appsettings.json for shared / cross-app settings.
  • Hierarchical keys with labels (prod, dev, test). Filter by label at load.
  • Key Vault references for secrets — App Config holds the reference; runtime resolves.
  • Push refresh via Service Bus or built-in polling for config changes without restart.
  • Feature flags — managed in portal; consumed via Microsoft.FeatureManagement library. Percentage, time-window, targeting filters.

Concepts (deep dive)

What App Configuration is

Azure App Configuration is a centralized key-value store for application settings, plus a feature-flag management system on top. It's the place a fleet of services reads its shared configuration from — connection strings, tuning knobs, feature toggles — instead of each service shipping its own appsettings.json and drifting over time. Like Key Vault but for non-secret config: cheaper, with richer filtering (labels, snapshots), and built for high-read traffic.

   ┌───────────────────────────────────────────┐
   │      Azure App Configuration store        │
   │   ┌────────────────────────────────────┐  │
   │   │  key/value/label rows              │  │
   │   │  MyApp:Db / "Server=…"    / prod   │  │
   │   │  MyApp:Db / "Server=dev…" / dev    │  │
   │   │  MyApp:NewBilling / true  / prod   │  │
   │   │  + Key Vault references            │  │
   │   │  + feature flags                   │  │
   │   │  + snapshots (immutable bundles)   │  │
   │   └────────────────────────────────────┘  │
   └───────────────────────────────────────────┘
                       ▲    │ change event
              load /   │    ▼ (push refresh)
              refresh  │  ┌──────────────────┐
                       └──┤ app instance(s)  │
                          │  IConfiguration  │
                          └──────────────────┘

Why centralize: a single source of truth across N services prevents the "edit appsettings on six pods and forget the seventh" failure mode. Labels let one store serve every environment; Key Vault references keep secrets in Key Vault while still threading them through the same config pipeline. Feature flags become data, not deployments.

Trade-off: every app now has a runtime dependency on a config service. Bootstrapping (the few seconds between process start and first config load), connectivity to Azure, and the cost of getting auth wrong all become production concerns.

App Configuration vs Key Vault vs Managed Identity (the decision frame)

These three services are constantly confused because they all touch "settings and secrets." They solve different problems and you typically use all three together:

What it stores Optimized for When to reach for it
App Configuration Non-secret config + feature flags High-read, labeled fetches, push refresh Centralized tuning knobs, flags, environment-keyed values
Key Vault Secrets, certs, keys Strong audit, RBAC per secret, hardware-protected keys Anything you wouldn't paste in a Slack channel
Managed Identity (Not storage) An Azure-assigned identity attached to the app Authenticating to other Azure services with no shared secret How the app proves who it is to App Config and Key Vault

The canonical pattern: the app uses managed identity to authenticate to App Configuration, where most settings live; secret-shaped settings are stored as Key Vault references that App Config resolves at fetch time using the same managed identity. No passwords in any config file, ever.

Eventual consistency (the refresh window)

App Configuration is not a push channel by default — your app polls for changes on the interval you set (commonly 30s). Between a change in the store and the next poll, the app is running on stale config. This is fine for most settings; it's a sharp edge for feature flags that gate a release, where a flag flip needs to take effect everywhere within seconds. Two ways out: shorten the polling interval (cheap, slightly more requests) or wire up push refresh via Service Bus / Event Grid (instant, more moving parts).

💡 The sentinel pattern below is the standard way to make polling cheap: poll one key; reload everything only when that key changes.

Setup

App Configuration plugs into ASP.NET Core's standard IConfiguration pipeline as another configuration provider — alongside appsettings.json, environment variables, and command-line args. After registration, every config["MyApp:Foo"] lookup transparently consults the store; the rest of the app doesn't know or care where the value came from.

builder.Configuration.AddAzureAppConfiguration(o =>
{
    o.Connect(new Uri("https://myappconfig.azconfig.io"), new DefaultAzureCredential())
        .Select("MyApp:*", labelFilter: "prod")
        .ConfigureKeyVault(kv => kv.SetCredential(new DefaultAzureCredential()))
        .UseFeatureFlags();
});
public class C(IConfiguration config) { var x = config["MyApp:Foo"]; }

Key structure

Keys are flat strings with : as a hierarchy convention (matching how .NET's IConfiguration flattens nested JSON). Labels are a separate dimension: the same key can have many values, one per label, and the app picks which label to load at startup. Labels are how one store serves dev / staging / prod from a single namespace without key-name collisions.

MyApp:Logging:LogLevel:Default
MyApp:ConnectionStrings:Db
MyApp:Features:NewBilling     (label: dev)
MyApp:Features:NewBilling     (label: prod, value: false)

Labels = environment.

Refresh

A running app's IConfiguration is a snapshot taken at startup. Refresh is the mechanism that updates it later without restarting the process. You declare which key(s) to watch and how often; the middleware in the request pipeline opportunistically checks for changes and reloads when the watched key has moved.

The sentinel pattern is the production-recommended setup: instead of watching every key (one request per key per poll), you watch a single dedicated key. When you want to roll a config change, update the actual keys first, then bump the sentinel last. The next poll detects the sentinel change and reloads everything as one atomic batch.

o.ConfigureRefresh(refresh =>
{
    refresh.Register("MyApp:Sentinel", refreshAll: true)   // sentinel pattern
        .SetRefreshInterval(TimeSpan.FromSeconds(30));
});
// In a middleware:
app.UseAzureAppConfiguration();

Sentinel: when the sentinel key changes, reload everything. Cheaper than polling all keys.

Push refresh (Service Bus)

Polling has a built-in latency floor (the polling interval) and a built-in cost (one request per poll, even when nothing has changed). Push refresh inverts the flow: App Configuration emits a change event to Event Grid / Service Bus when a key is updated, and the subscribed app reloads on receipt. Latency drops from "interval" to "milliseconds"; idle traffic drops to zero.

App Configuration publishes change events; subscribed app reloads instantly. No polling.

Feature flags

A feature flag is a named boolean (or, with variants, a named multi-value) whose value is decided at runtime, not deploy time. The app branches on the flag; flipping the flag in App Configuration changes the branch globally without a redeploy. This decouples release (the code is in production) from rollout (the code is reachable for some users).

Filters are how flags become more than booleans: a flag can be "on for 10% of users" (Percentage), "on between two dates" (TimeWindow), or "on for users in this group" (Targeting). Filters evaluate at every check, so the answer can change per request without redeploying.

builder.Services.AddFeatureManagement();

public class C(IFeatureManager fm)
{
    public async Task M()
    {
        if (await fm.IsEnabledAsync("NewBilling"))
            return /* new path */;
        return /* old path */;
    }
}

Filters (built-in): - Percentage: 10% of users. - TimeWindow: enable between dates. - Targeting: specific users / groups.

Custom filters via IFeatureFilter.

Targeting filter

Targeting binds the flag's audience to who is asking — specific user IDs, group memberships, and percentage rollouts within those groups. The library needs an ITargetingContextAccessor to know which user the current request belongs to (usually pulled from HttpContext.User); without it, targeting falls back to the default rollout percentage.

{
  "Audience": {
    "Users": ["[email protected]"],
    "Groups": [{ "Name": "BetaTesters", "RolloutPercentage": 50 }],
    "DefaultRolloutPercentage": 5
  }
}
public class TargetingContextAccessor : ITargetingContextAccessor
{
    public ValueTask<TargetingContext> GetContextAsync()
        => new(new TargetingContext { UserId = ..., Groups = ... });
}

Variants (advanced flags)

A variant flag carries a value ("blue", "green", "control", "treatment") instead of just on/off, with rules for which audience gets which variant. This is the building block for A/B tests and gradual rollouts of multi-option choices.

var variant = await fm.GetVariantAsync("ButtonColor");
return variant.Name;   // "blue" or "green"

Key Vault references

A Key Vault reference is a special App Config value whose body is a pointer ({ "uri": "https://kv/.../secrets/X" }) instead of the secret itself. At fetch time, the App Configuration client recognizes the reference type, calls Key Vault using the app's own credential, and substitutes the resolved secret value into IConfiguration. The app sees a plain string; the secret never sits in App Config.

key: ConnectionStrings:Db
value: { "uri": "https://mykv.vault.azure.net/secrets/Db" }

Stored as JSON metadata. Loaded as the actual secret value at runtime via Key Vault.

Snapshots

A snapshot is an immutable, point-in-time bundle of keys + values + labels. Once created, it cannot be edited. Apps configured to load from a snapshot get a frozen view; the live store can keep moving without affecting them. This is how you do atomic config releases: prepare snapshot N+1, point apps at it on a deploy, roll back by pointing them at N.

Immutable point-in-time of a set of keys. Use for atomic deploys — your app reads from snapshot N until snapshot N+1 is ready.

Tiers

Tier Notes
Free Limited operations
Standard $1.20/mo + reqs; reasonable for most

Pitfalls

  • Refresh in middleware: forgetting app.UseAzureAppConfiguration() → never refreshes.
  • Static cache: feature flag evaluated once → stale. Use IFeatureManagerSnapshot for per-request.
  • Lots of keys, no labels: hard to manage envs.

Bicep

resource appconfig 'Microsoft.AppConfiguration/configurationStores@2024-05-01' = {
  name: 'myappconfig'
  sku: { name: 'standard' }
}

resource setting 'Microsoft.AppConfiguration/configurationStores/keyValues@2024-05-01' = {
  parent: appconfig
  name: 'MyApp:Foo$prod'   // $ separates label
  properties: { value: 'bar' }
}

Local dev

For local dev, fall back to appsettings.json if App Config not available, or use a separate dev App Config namespace.

if (!builder.Environment.IsDevelopment())
    builder.Configuration.AddAzureAppConfiguration(/* ... */);

Cost

Mostly cheap (~$1.20/mo + per-op). Compared to Key Vault, fewer requests because labels filter at fetch.


Code: correct vs wrong

❌ Wrong: hardcoded feature flag check

if (Environment.GetEnvironmentVariable("FEATURE_X") == "true") ...

✅ Correct: FeatureManager

if (await fm.IsEnabledAsync("FeatureX")) ...

❌ Wrong: forgetting middleware refresh

o.ConfigureRefresh(...)
// but no app.UseAzureAppConfiguration()

✅ Correct: middleware

app.UseAzureAppConfiguration();

Design patterns for this topic

Pattern 1 — "Centralized config across services"

  • Intent: consistent config.

Pattern 2 — "Sentinel for refresh"

  • Intent: atomic reload signal.

Pattern 3 — "Push refresh via Service Bus"

  • Intent: instant change propagation.

Pattern 4 — "Snapshots for atomic deploys"

  • Intent: consistent multi-key changes.

Pattern 5 — "Targeting feature flags"

  • Intent: progressive rollout.

Pros & cons / trade-offs

Aspect Pros Cons
App Config Centralized; flags Network dep
Per-app config Local Drift across services
Key Vault Secrets Not great for non-secret config

When to use / when to avoid

  • Use for shared config / flags across services.
  • Use with managed identity.
  • Avoid as secret store — use Key Vault references.
  • Avoid for local single-app where appsettings sufficient.

Interview Q&A

Q1. App Config vs Key Vault? App Config: non-secret config + feature flags. Key Vault: secrets. Often paired (App Config refs to Key Vault).

Q2. Labels? Filter at fetch time — environment / version. Same key, different value per label.

Q3. Sentinel pattern? Single key whose change triggers reload of everything else. Cheaper than polling all keys.

Q4. Push refresh? Service Bus subscription notifies app of config change → instant reload.

Q5. Feature flag filters? Percentage, TimeWindow, Targeting. Custom via IFeatureFilter.

Q6. Variants? Different flag values per audience for A/B testing.

Q7. Snapshots? Immutable named point-in-time of keys. Atomic releases.

Q8. IFeatureManager vs IFeatureManagerSnapshot? IFeatureManager: per-call evaluation. Snapshot: per-request consistent.

Q9. Local dev? Skip App Config; use appsettings.json. Or separate dev store.

Q10. Refresh middleware? UseAzureAppConfiguration(). Forgotten = no refresh.

Q11. Targeting context? ITargetingContextAccessor provides user/groups for targeted flags.

Q12. Cost? Cheap (~$1.20/mo + reqs). Free tier for small apps.


Gotchas / common mistakes

  • ⚠️ Forgot refresh middleware.
  • ⚠️ No labels — env contamination.
  • ⚠️ Storing secrets directly instead of KV reference.
  • ⚠️ Single instance feature flag check — stale across instances.

Further reading