Runtime Configuration

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Commands and behaviour below are current for Azure CLI 2.90.0 (checked 2026-09-26); re-verify against az version and the linked reference pages before relying on them in a pipeline.

The problem: three kinds of values, one per environment

Every one of Bookshelf’s services — catalog-api, orders-api, notifier — needs values at startup and at runtime that fall into three different categories, each with a different owner and a different change cadence:

  • Plain configuration — a log level, an external endpoint URL, a page size, a timeout. Not sensitive; safe to read in plaintext, safe to show in a diff.

  • Secrets — a database connection string’s credential portion, a third-party API key. Never plaintext at rest, never in source control, access itself audited. Covered in depth on Key Vault and Secrets; this page only covers how the mechanisms below reference a secret, not how Key Vault stores or rotates one.

  • Feature flags — a boolean (or multi-variant) switch that changes behaviour without changing a build, so a feature can ship dark and be turned on for a subset of traffic without a deployment.

All three also vary per environment — catalog-api in dev polls a sandbox payment gateway with a relaxed timeout; the same code in prod points at the real gateway with a strict one — and, increasingly, teams want to change any of these without redeploying: flip a flag, raise a timeout, roll a secret, and have running instances pick it up on their own. That is what the rest of this page covers: one Azure-native store built for exactly this job (App Configuration), and the platform-specific mechanisms — Container Apps, App Service, and Kubernetes on AKS — that either consume it directly or offer their own simpler alternative for the same three categories.

Azure App Configuration

Azure App Configuration is a managed key-value store purpose-built for centralizing an application’s configuration and feature flags — separately from its secrets, which stay in Key Vault and are only referenced from here.

Tiers

App Configuration currently ships four tiers — re-verify against the pricing page before quoting a number, since limits move between reviews of this page:

Tier What it is for

Free

No cost; one store per subscription; 10 MB of key-value storage plus 10 MB of snapshot storage; about 1,000 requests/day. Good for a single evaluation store, not for Bookshelf’s dev environment once more than one developer shares it.

Developer

A low-cost tier for non-production use, with a daily request allowance around 3,000 and Private Link support —  the tier Bookshelf uses for appcs-bookshelf-dev.

Standard

The production tier: 1 GB of key-value storage plus 1 GB of snapshot storage, roughly 30,000 requests/hour, geo-replication, and a 99.9% SLA (99.95% with geo-replication enabled). Bookshelf’s appcs-bookshelf production store is Standard.

Premium

Adds a higher SLA (99.99% with geo-replication enabled) and higher throughput/scale ceilings for large fleets; reach for it only once Standard’s request quota is the actual bottleneck.

Soft delete is not available on Free or Developer — deleting one of those stores is immediate and final. On Standard and Premium, soft delete is enabled automatically with a retention period (up to 7 days, fixed at creation and not changeable afterwards), and purge protection can additionally be turned on so a soft-deleted store cannot be purged early even by someone with the permission to do so — see Clean up.

In the Azure portal

  1. Search for App Configuration and select Create.

  2. On Basics, choose $RG, a store name (appcs-bookshelf), a region, and a Pricing tier (Standard for Bookshelf’s production store).

  3. Review and select Create.

  4. Once created, the store’s left navigation exposes Configuration explorer (key-values, labels, Key Vault references), Feature manager (feature flags and their filters/variants), Snapshots, and Geo-replication.

With the Azure CLI

az appconfig create \
  --name appcs-bookshelf \
  --resource-group "$RG" \
  --location "$LOCATION" \
  --sku Standard

Key-values, labels, feature flags, and Key Vault references

A key-value is a name/value pair (catalog-api:retry:max-attempts = 3). A label partitions the same key across environments or slices without needing separate stores — Bookshelf uses dev, staging, and prod as labels on the same keys in the one appcs-bookshelf store, and every read filters by label to pick the right slice.

# Plain key-value, labeled for the dev environment
az appconfig kv set \
  --name appcs-bookshelf \
  --key "catalog-api:retry:max-attempts" \
  --label dev \
  --value "3" \
  --yes

az appconfig kv list --name appcs-bookshelf --label dev --output table

A feature flag is a specially-shaped key (prefixed .appconfig.featureflag/) holding an on/off state plus optional filters (percentage rollout, targeting a group) and variants for more than a plain boolean:

# Create/update the flag definition (off by default)
az appconfig feature set \
  --name appcs-bookshelf \
  --feature CatalogRecommendations \
  --label prod

# Turn it on for the label that matters
az appconfig feature enable \
  --name appcs-bookshelf \
  --feature CatalogRecommendations \
  --label prod

Derived from Azure CLI reference — az appconfig feature. In the portal, the same operations live under Feature manager rather than Configuration explorer.

A Key Vault reference is a key-value whose stored content is a pointer to a secret in kv-bookshelf, not the secret’s value — the App Configuration store itself never sees the plaintext:

az appconfig kv set-keyvault \
  --name appcs-bookshelf \
  --key "orders-api:db:connection-string" \
  --label prod \
  --secret-identifier "https://kv-bookshelf.vault.azure.net/secrets/orders-db-connection-string"

Derived from Azure CLI reference — az appconfig kv (the set-keyvault subcommand). The reader still needs Key Vault Secrets User on kv-bookshelf — see Identity and Access and Key Vault and Secrets — App Configuration only resolves which secret to fetch, not the permission to read it.

A snapshot freezes the current state of a filtered set of key-values (and feature flags) as an immutable, named point-in-time view — the mechanism Bookshelf uses to pin exactly what catalog-api read at the moment a release shipped, for reproducible rollbacks and audits:

az appconfig snapshot create \
  --name appcs-bookshelf \
  --snapshot release-2026-09-27 \
  --filters "[{\"key\":\"catalog-api:*\",\"label\":\"prod\"}]"

Import and export

az appconfig kv import/kv export move key-values (and, unless --skip-features is passed, feature flags) between a store, a file (JSON/YAML/properties), another App Configuration store, or an App Service application’s own settings — the tool Bookshelf uses to seed appcs-bookshelf-dev from a checked-in baseline file, and to copy a snapshot of prod values into a new store for a fire drill:

# Seed dev from a checked-in baseline file (never containing real secret values --
# secrets are Key Vault references, which import/export as pointers, not plaintext)
az appconfig kv import \
  --name appcs-bookshelf-dev \
  --source file \
  --path bookshelf-baseline.json \
  --format json \
  --label dev

# Export everything under label prod to another store, preserving labels
az appconfig kv export \
  --name appcs-bookshelf \
  --destination appconfig \
  --dest-name appcs-bookshelf-drill \
  --key "*" --label prod --preserve-labels

Derived from Azure CLI reference — az appconfig kv (the import/export subcommands).

Geo-replication

Geo-replication (Standard and Premium tiers) adds one or more read/write replicas of the same store in other regions; an SDK or the Kubernetes provider below can fail over to a healthy replica automatically (replicaDiscoveryEnabled) if the primary region has an outage, without Bookshelf’s own code choosing a different endpoint:

az appconfig replica create \
  --name appcs-bookshelf \
  --resource-group "$RG" \
  --replica-name appcs-bookshelf-westus \
  --location westus

Dynamic refresh: sentinel key, interval, and push

Reading a key-value once at startup is no different from reading any other configuration file — the value that changes anything here is picking up an edit while the process keeps running. Every consumer below does this the same conceptual way: poll (or get pushed) a sentinel key — one key-value whose only job is to change every time anything else in the same label changed — and only re-read the rest of the configuration once the sentinel’s own value has moved, instead of re-reading (and re-validating) every key on every check.

# Bump the sentinel after any batch of related changes -- a timestamp or a GUID both work
az appconfig kv set \
  --name appcs-bookshelf \
  --key "catalog-api:sentinel" \
  --label prod \
  --value "$(date -u +%Y%m%dT%H%M%SZ)" \
  --yes

By default every consumer polls on an interval (30 seconds is the common default across the Spring, .NET, and Kubernetes providers below) rather than being told immediately; a push-based alternative exists too — subscribing to App Configuration’s Event Grid Microsoft.AppConfiguration.KeyValueModified events lets a consumer react to a change within seconds instead of waiting out the interval, at the cost of wiring an Event Grid subscription and a handler that forces an out-of-cycle refresh check.

sequenceDiagram participant Ops as Operator participant AC as appcs-bookshelf participant EG as Event Grid participant App as catalog-api instance Ops->>AC: az appconfig kv set (value + sentinel) AC-->>EG: KeyValueModified event (push path, optional) EG-->>App: notify subscriber App->>AC: re-read sentinel (poll path: on next interval tick instead) AC-->>App: sentinel changed App->>AC: re-read the rest of the selected key-values App->>App: rebind @RefreshScope / IOptionsSnapshot / regenerate ConfigMap

Java: the Spring Cloud Azure App Configuration starter

catalog-api and orders-api add spring-cloud-azure-appconfiguration-config (or the -config-web variant, which also refreshes on incoming HTTP activity rather than only on an explicit call) and select a sentinel key through monitoring:

<dependency>
    <groupId>com.azure.spring</groupId>
    <artifactId>spring-cloud-azure-appconfiguration-config-web</artifactId>
</dependency>
spring:
  cloud:
    azure:
      appconfiguration:
        stores:
          - endpoint: https://appcs-bookshelf.azconfig.io
            selects:
              - key-filter: catalog-api:*
                label-filter: prod
            monitoring:
              enabled: true
              refresh-interval: 30s
              triggers:
                - key: catalog-api:sentinel
                  label: prod

Refreshing re-binds every @RefreshScope bean from the updated Environment — the same mechanism Configuration and Profiles covers in full under its own @RefreshScope section; this page only adds the App Configuration-specific trigger (the sentinel key) on top of it, it does not repeat how @RefreshScope itself works.

.NET: the App Configuration provider

The equivalent ASP.NET Core wiring calls ConfigureRefresh with the same sentinel-key idea (Register, or RegisterAll to reload the whole selection on any watched key’s change) and reads the result through IOptionsSnapshot<T> rather than IOptions<T>, since IOptions<T> is only ever bound once at startup:

builder.Configuration.AddAzureAppConfiguration(options =>
{
    options.Connect(new Uri("https://appcs-bookshelf.azconfig.io"), new DefaultAzureCredential())
           .Select("OrdersApi:*", LabelFilter.Null)
           .ConfigureRefresh(refresh => refresh
               .Register("OrdersApi:Sentinel", refreshAll: true)
               .SetRefreshInterval(TimeSpan.FromSeconds(30)));
});

builder.Services.AddAzureAppConfiguration();
// ...
app.UseAzureAppConfiguration();

Refresh here is request-driven: the middleware only checks for a change when an incoming request arrives, and an idle app does not refresh on its own. See Configuration and the Options Pattern for IOptionsSnapshot<T> vs. IOptionsMonitor<T> vs. IOptions<T> in general; this page only adds the App Configuration provider’s own ConfigureRefresh/sentinel-key mechanics on top of that pattern.

Container Apps: env vars and secretref:

A Container App’s template.containers[].env list is the plain-configuration path: name/value pairs set at deploy time, either literal or secretref:<secret-name> pointing at a secret defined at the application level (itself optionally backed by a Key Vault reference or a managed identity — see Key Vault and Secrets and Container Apps for how `catalog-api’s Secrets blade and managed identity fit together).

az containerapp update \
  --name catalog-api \
  --resource-group "$RG" \
  --set-env-vars \
    "CATALOG_LOG_LEVEL=info" \
    "CATALOG_DB_CONNECTION_STRING=secretref:catalog-db-connection-string"

Secrets themselves are scoped to the application, not to any one revision — adding, changing, or removing a secret does not, by itself, create a new revision. An env-var change is a revision-scope change: --set-env- vars above creates a new revision with the updated value, and Bookshelf’s existing revision keeps running its old value until traffic is shifted (see the revision/traffic-splitting mechanics on Container Apps). Rotating the value a secret points to without changing which env var references it does not spawn a new revision on its own; the running replica keeps its old in-memory value until it is restarted or a revision-scope change is made for another reason. catalog-api does not use App Configuration’s dynamic-refresh push directly for its Container Apps env vars — the Spring Cloud Azure starter above talks to appcs-bookshelf from inside the running process instead, so refresh happens without a new revision at all; only the Key Vault-backed database secret needs a revision (or restart) to rotate.

App Service: app settings, connection strings, and slot settings

App settings are App Service’s equivalent of Container Apps env vars — injected into the process environment at startup — and connection strings are a typed subset of the same mechanism, surfaced to .NET through ConnectionStrings:<name> and to other stacks as an environment variable with a provider-specific prefix (SQLCONNSTR_, MYSQLCONNSTR_, …​). Either can itself be a Key Vault reference (@Microsoft.KeyVault(SecretUri=…​)), resolved by the platform using the Web App’s managed identity, the same pattern App Configuration uses internally.

az webapp config appsettings set \
  --name bookshelf-admin-web \
  --resource-group "$RG" \
  --settings \
    "CatalogApiBaseUrl=https://catalog-api.internal.bookshelf" \
    "OrdersDbConnectionString=@Microsoft.KeyVault(SecretUri=https://kv-bookshelf.vault.azure.net/secrets/orders-db-connection-string/)"

A slot setting is an app setting or connection string flagged to stay attached to its slot rather than travel with the swap covered on App Service and Azure Functions — the mechanism Bookshelf uses so `staging’s own database connection string never becomes `production’s after a swap:

az webapp config appsettings set \
  --name bookshelf-admin-web \
  --resource-group "$RG" \
  --slot-settings "Environment=staging" \
  --slot staging

Neither app settings nor connection strings refresh on their own once the process is running — changing one restarts the Web App (or, for a slot, only that slot) to pick it up; there is no in-process poll/push mechanism equivalent to the sentinel-key pattern above unless the application code itself also wires the App Configuration provider directly, as the .NET snippet earlier in this page does.

Kubernetes ConfigMaps on AKS

Plain ConfigMaps

A ConfigMap is Kubernetes' own plain-configuration object — key/value data mounted into a Pod either as environment variables (envFrom) or as files (a volume mount), authored and applied like any other manifest:

apiVersion: v1
kind: ConfigMap
metadata:
  name: catalog-api-config
  namespace: bookshelf
data:
  CATALOG_LOG_LEVEL: "info"
  CATALOG_PAGE_SIZE: "20"
kubectl apply -f catalog-api-config.yaml

A ConfigMap consumed as environment variables does not update a running Pod’s environment when the ConfigMap changes underneath it — an env var is only read at container start, so the Deployment needs a rollout (kubectl rollout restart deployment/catalog-api, or a checksum annotation on the Pod template that changes whenever the ConfigMap’s content does, so a normal `kubectl apply triggers the rollout on its own). A ConfigMap consumed as a mounted volume is the one case that updates in place — kubelet syncs the mounted files on its own periodic sync, typically within a minute — but the application still has to notice the file changed (a file watcher, or its own poll) to act on it; nothing pushes an in-process event the way @RefreshScope or IOptionsSnapshot<T> do above.

Spring Boot’s own ConfigMap/Secret consumption — spring-cloud-starter-kubernetes-client-config, its watch-based reload, and the RBAC role it needs — is covered in full on Configuration and Profiles's "Kubernetes ConfigMaps and Secrets" section; this page does not repeat that mechanism, only where the ConfigMap/Secret it reads comes from.

The Azure App Configuration Kubernetes provider

Rather than hand-authoring ConfigMap/Secret manifests and keeping them in sync with appcs-bookshelf by hand, the Azure App Configuration Kubernetes provider runs as an operator inside the cluster and generates both from an AzureAppConfigurationProvider custom resource, refreshing them on the same poll-a-sentinel-or-a-monitored-key pattern as the Spring and .NET providers above:

helm install azureappconfiguration.kubernetesprovider \
  oci://mcr.microsoft.com/azure-app-configuration/helmchart/kubernetes-provider \
  --namespace azappconfig-system \
  --create-namespace
apiVersion: azconfig.io/v1
kind: AzureAppConfigurationProvider
metadata:
  name: catalog-api-config
  namespace: bookshelf
spec:
  endpoint: https://appcs-bookshelf.azconfig.io
  target:
    configMapName: catalog-api-config
  auth:
    workloadIdentity:
      serviceAccountName: catalog-api
  configuration:
    selectors:
      - keyFilter: "catalog-api:*"
        labelFilter: prod
    refresh:
      enabled: true
      interval: 30s
      monitoring:
        keyValues:
          - key: "catalog-api:sentinel"
            label: prod
  secret:
    target:
      secretName: catalog-api-secrets
    auth:
      workloadIdentity:
        serviceAccountName: catalog-api
    refresh:
      enabled: true
      interval: 1h

The auth.workloadIdentity block above authenticates the provider using the same federated-credential pattern catalog-api already uses elsewhere on AKS — see Deploying Microservices on AKS's workload identity section and Identity and Access for how the token exchange itself works; nothing new is introduced here beyond pointing the same ServiceAccount at the provider’s own custom resource. Any Key Vault references selected by spec.secret resolve into the generated Secret, refreshed independently of the ConfigMap on spec.secret.refresh.interval, since secret rotation and configuration change are different events with different acceptable staleness windows.

The provider only regenerates the ConfigMap/Secret objects — it still leaves the same problem plain ConfigMap`s have: a Deployment consuming them as environment variables needs a rollout to see the new values, which the provider does not trigger on its own. Consuming the generated `ConfigMap as a mounted volume (as above) is the way to actually get an in-place update without a rollout.

Choosing a mechanism

Mechanism Best for Refresh without redeploy Secrets

Azure App Configuration (direct SDK/starter)

App-level config + feature flags shared across several services/environments, with labels and snapshots

Yes — sentinel key, poll or Event Grid push

Via Key Vault reference only, never stored directly

Container Apps env vars / secretref:

Container-level values scoped to one Container App, changed through a redeploy/new revision

No (env var); the in-process App Configuration starter above is what actually refreshes without one

secretref: only, backed by a Key Vault reference or managed identity

App Service app settings / slot settings

Web App/Functions-level values, with slot-settings for values that must not travel on swap

No — restarts the Web App (or slot) to apply

Key Vault reference (@Microsoft.KeyVault(…​)) resolved via managed identity

Plain Kubernetes ConfigMaps

Cluster-native manifests with no external dependency, fine for values that change rarely

Only for a mounted-volume consumer; env-var consumers need a rollout

Kubernetes `Secret`s only — typed by hand or via the Key Vault CSI driver, not from App Configuration

App Configuration Kubernetes provider

The AKS equivalent of the Spring/.NET starters: centralizes config+flags in appcs-bookshelf while still producing native ConfigMap/Secret objects

Yes for the generated objects themselves — a mounted-volume consumer still updates in place; an env-var consumer still needs a rollout

Generates a Secret from Key Vault references, refreshed on its own interval

Clean up

# Delete the whole resource group, including appcs-bookshelf (soft-deleted afterwards on Standard/Premium)
az group delete --name "$RG" --yes --no-wait

# If purge protection is NOT enabled, purge the soft-deleted store immediately instead of waiting
# out the retention period (requires the purge permission at subscription scope)
az appconfig purge --name appcs-bookshelf

Derived from Soft delete in Azure App Configuration and Recover Azure App Configuration stores. If purge protection is enabled on the store, the soft-deleted copy cannot be purged before its retention period elapses regardless of permissions — that setting cannot be turned off once enabled, so decide it at store-creation time for any store that must be fully gone quickly during iteration.

App Configuration store feeding Container Apps via secretref