Skip to content
Get Started for Free

App Configuration

Azure App Configuration is a managed service for centralizing application settings and feature flags. It stores configuration as key-values that can be filtered by label, versioned through revisions, frozen into point-in-time snapshots, and referenced against secrets held in Azure Key Vault. For more information, see What is Azure App Configuration.

LocalStack for Azure provides a local environment for building and testing applications that make use of Azure App Configuration. The supported APIs are available on our API Coverage section, which provides information on the extent of App Configuration’s integration with LocalStack.

This guide is designed for users new to App Configuration and assumes basic knowledge of the Azure CLI and our lstk az proxy. The Observe RBAC enforcement section additionally requires jq to read a claim out of an access token.

Launch LocalStack using your preferred method. For more information, see Introduction to LocalStack for Azure. Once the container is running, enable Azure CLI interception by running:

Terminal window
lstk az start-interception

This command points the az CLI away from the public Azure management REST API and toward the LocalStack for Azure emulator API. To revert this configuration, run:

Terminal window
lstk az stop-interception

This reconfigures the az CLI to send commands to the official Azure management REST API.

Create a resource group to hold all resources created in this guide:

Terminal window
az group create \
--name rg-appconfig-demo \
--location westeurope
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-appconfig-demo",
"location": "westeurope",
"name": "rg-appconfig-demo",
"properties": {
"provisioningState": "Succeeded"
},
"type": "Microsoft.Resources/resourceGroups"
}

Create a configuration store. The store name is a DNS label: alphanumerics and hyphens only, between 5 and 50 characters, and globally unique:

Terminal window
az appconfig create \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--location westeurope \
--sku Standard \
--retention-days 7 \
--tags environment=demo
Output
{
"creationDate": "2026-09-16T14:11:29.455779+00:00",
"defaultKeyValueRevisionRetentionPeriodInSeconds": 2592000,
"disableLocalAuth": false,
"enablePurgeProtection": false,
"endpoint": "https://appconfig-demo-localstack.azure.localhost.localstack.cloud:4566",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-appconfig-demo/providers/Microsoft.AppConfiguration/configurationStores/appconfig-demo-localstack",
"location": "westeurope",
"name": "appconfig-demo-localstack",
"provisioningState": "Succeeded",
"resourceGroup": "rg-appconfig-demo",
"sku": {
"name": "standard"
},
"softDeleteRetentionInDays": 7,
"tags": {
"environment": "demo"
},
"type": "Microsoft.AppConfiguration/configurationStores"
...
}

The endpoint property is the data-plane address that SDKs and configuration providers use verbatim. Clients should read it back from the service rather than build it from the store name.

Retrieve a single store:

Terminal window
az appconfig show \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--query "{Name:name, Sku:sku.name, Endpoint:endpoint, Retention:softDeleteRetentionInDays}"
Output
{
"Endpoint": "https://appconfig-demo-localstack.azure.localhost.localstack.cloud:4566",
"Name": "appconfig-demo-localstack",
"Retention": 7,
"Sku": "standard"
}

List every store in a resource group:

Terminal window
az appconfig list \
--resource-group rg-appconfig-demo \
--query "[].{Name:name, Location:location, Sku:sku.name, State:provisioningState}" \
--output table
Output
Name Location Sku State
------------------------- ---------- -------- ---------
appconfig-demo-localstack westeurope standard Succeeded

Update the store to change its tags:

Terminal window
az appconfig update \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--tags environment=demo owner=platform \
--query "tags"
Output
{
"environment": "demo",
"owner": "platform"
}

Every store is created with four access keys: a primary and secondary pair with read and write access, and a read-only pair. List them:

Terminal window
az appconfig credential list \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--query "[].{Name:name, Id:id, ReadOnly:readOnly}" \
--output table
Output
Name Id ReadOnly
------------------- ---------------------- ----------
Primary MujyyVBRNGLykfLG3RkB1A False
Secondary DfbAmyIZEVAx9iE6KnBt9A False
Primary Read Only wxWGWZrgcR7VP8oyubuQ8Q True
Secondary Read Only LarYNCfPTx0XPIFzNsj_8g True

Each key carries a ready-to-use connection string in the form Endpoint=<endpoint>;Id=<id>;Secret=<secret>. Capture the secondary key’s identifier and regenerate it:

Terminal window
SECONDARY_KEY_ID=$(az appconfig credential list \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--query "[?name=='Secondary'].id | [0]" \
--output tsv)
az appconfig credential regenerate \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--id="$SECONDARY_KEY_ID" \
--query "{Name:name, Id:id, ReadOnly:readOnly}"
Output
{
"Id": "hSS0LdDUJPo7oR8BXT3BdA",
"Name": "Secondary",
"ReadOnly": false
}

Regenerating a key replaces the whole credential for that slot: both the identifier and the secret change, while the name and the read-only flag are preserved. Azure behaves the same way, so the identifier in the output above differs from the one that was listed. The other three keys are unaffected, so a running application using the primary key keeps working.

A key-value is identified by its key and an optional label. The same key can carry a different value under each label, which is how per-environment configuration is modelled:

Terminal window
az appconfig kv set \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--value blue \
--content-type "text/plain" \
--tags tier=frontend \
--yes
Output
{
"contentType": "text/plain",
"etag": "fba4697ee4724387aa9b15f061d9b433",
"key": "app/settings/color",
"label": "production",
"lastModified": "2026-09-16T14:11:33.819984+00:00",
"locked": false,
"tags": {
"tier": "frontend"
},
"value": "blue"
}

Read a single key-value back:

Terminal window
az appconfig kv show \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production
Output
{
"contentType": "text/plain",
"etag": "fba4697ee4724387aa9b15f061d9b433",
"key": "app/settings/color",
"label": "production",
"lastModified": "2026-09-16T14:11:33.819984+00:00",
"locked": false,
"tags": {
"tier": "frontend"
},
"value": "blue"
}

Add a second key-value so the listing below has more than one entry:

Terminal window
az appconfig kv set \
--name appconfig-demo-localstack \
--key "app/settings/size" \
--label production \
--value large \
--yes
Output
{
"contentType": "",
"etag": "75505fc1d5004e009e85563ec9b32f0c",
"key": "app/settings/size",
"label": "production",
"lastModified": "2026-09-16T14:11:34.703800+00:00",
"locked": false,
"tags": {},
"value": "large"
}

List key-values, filtering by a key prefix. Both --key and --label accept a star as a wildcard, and --label also accepts a comma-separated list:

Terminal window
az appconfig kv list \
--name appconfig-demo-localstack \
--key "app/settings/*" \
--label production \
--all \
--output table
Output
CONTENT TYPE KEY VALUE LAST MODIFIED TAGS LABEL LOCKED
-------------- ------------------ ------- -------------------- -------------------- ---------- --------
text/plain app/settings/color blue 2026-09-16T14:11:33Z {'tier': 'frontend'} production False
app/settings/size large 2026-09-16T14:11:34Z {} production False

Every write creates a new revision. Update app/settings/color:

Terminal window
az appconfig kv set \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--value red \
--yes \
--query "value"
Output
"red"

Both versions are now visible, newest first:

Terminal window
az appconfig revision list \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--all \
--output table
Output
CONTENT TYPE KEY VALUE LAST MODIFIED TAGS LABEL LOCKED
-------------- ------------------ ------- -------------------- -------------------- ---------- --------
text/plain app/settings/color red 2026-09-16T14:11:37Z {'tier': 'frontend'} production False
text/plain app/settings/color blue 2026-09-16T14:11:33Z {'tier': 'frontend'} production False

Locking a key-value makes it read-only, which protects a setting from accidental change:

Terminal window
az appconfig kv lock \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--yes \
--query "locked"
Output
true

A write to a locked key-value is rejected:

Terminal window
az appconfig kv set \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--value green \
--yes
Output
ERROR: Failed to update read only key-value. Unlock the key-value before updating it.

Unlock it to allow writes again:

Terminal window
az appconfig kv unlock \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--yes \
--query "locked"
Output
false

Because every write is retained as a revision, the store can be read as it stood at an earlier moment. Capture the current key-value’s timestamp and trim it to whole seconds, which is the only precision --datetime accepts:

Terminal window
LAST_MODIFIED=$(az appconfig kv show \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--query lastModified \
--output tsv)
CUTOFF="${LAST_MODIFIED%%.*}"
CUTOFF="${CUTOFF%%+*}Z"
echo "$CUTOFF"
Output
2026-09-16T14:11:37Z

Wait for the next second before writing again. A whole-second cutoff covers the whole of that second, so a write made inside it would fall within the window being queried and would be returned instead of the earlier value:

Terminal window
sleep 1

Change the value, so there is something to look back past:

Terminal window
az appconfig kv set \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--value amber \
--yes \
--query "value"
Output
"amber"

Pass the captured instant to --datetime to read the store as it stood before that change:

Terminal window
az appconfig kv list \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--datetime "$CUTOFF" \
--output table
Output
CONTENT TYPE KEY VALUE LAST MODIFIED TAGS LABEL LOCKED
-------------- ------------------ ------- -------------------- -------------------- ---------- --------
text/plain app/settings/color red 2026-09-16T14:11:37Z {'tier': 'frontend'} production False

The same listing without --datetime returns the current value:

Terminal window
az appconfig kv list \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--output table
Output
CONTENT TYPE KEY VALUE LAST MODIFIED TAGS LABEL LOCKED
-------------- ------------------ ------- -------------------- -------------------- ---------- --------
text/plain app/settings/color amber 2026-09-16T14:11:42Z {'tier': 'frontend'} production False

A snapshot freezes the key-values matching a filter at the moment it is created, giving an application a configuration set that cannot shift underneath it:

Terminal window
az appconfig snapshot create \
--name appconfig-demo-localstack \
--snapshot-name baseline \
--filters '{"key":"app/settings/*","label":"production"}'
Output
{
"compositionType": "key",
"created": "2026-09-16T14:11:43.730861+00:00",
"etag": "49c270f7426ed076c6387c4bdc45afaa",
"expires": null,
"filters": [
{
"key": "app/settings/*",
"label": "production"
}
],
"itemsCount": 2,
"name": "baseline",
"retentionPeriod": 2592000,
"size": 1000,
"status": "ready",
"tags": {}
}

List the snapshots on a store:

Terminal window
az appconfig snapshot list \
--name appconfig-demo-localstack \
--query "[].{Name:name, Status:status, Items:itemsCount, Created:created}" \
--output table
Output
Name Status Items Created
-------- -------- ------- --------------------------------
baseline ready 2 2026-09-16T14:11:43.730861+00:00

A snapshot is materialized once, when it is created, so changing a key-value afterwards does not affect it. Change app/settings/color again:

Terminal window
az appconfig kv set \
--name appconfig-demo-localstack \
--key "app/settings/color" \
--label production \
--value violet \
--yes \
--query "value"
Output
"violet"

The store now holds violet, but listing through the snapshot still returns the value captured when it was created:

Terminal window
az appconfig kv list \
--name appconfig-demo-localstack \
--snapshot baseline \
--output table
Output
CONTENT TYPE KEY VALUE LAST MODIFIED TAGS LABEL LOCKED
-------------- ------------------ ------- -------------------- -------------------- ---------- --------
text/plain app/settings/color amber 2026-09-16T14:11:42Z {'tier': 'frontend'} production False
app/settings/size large 2026-09-16T14:11:34Z {} production False

Archive a snapshot to mark it for expiry, and recover it to cancel that:

Terminal window
az appconfig snapshot archive \
--name appconfig-demo-localstack \
--snapshot-name baseline \
--query "{Status:status, Expires:expires}"
Output
{
"Expires": "2026-10-16T14:11:53.597151+00:00",
"Status": "archived"
}
Terminal window
az appconfig snapshot recover \
--name appconfig-demo-localstack \
--snapshot-name baseline \
--query "{Status:status, Expires:expires}"
Output
{
"Expires": null,
"Status": "ready"
}

An archived snapshot still serves its contents. Archiving starts the retention clock; recovering stops it.

Rather than storing a secret in App Configuration, store a reference to it in Key Vault. Create a vault that uses Azure RBAC, which is the permission model the Key Vault Secrets User role requires:

Terminal window
az keyvault create \
--name kv-appconfig-demo \
--resource-group rg-appconfig-demo \
--location westeurope \
--enable-rbac-authorization true \
--retention-days 7 \
--query "{Name:name, Uri:properties.vaultUri, Rbac:properties.enableRbacAuthorization}"
Output
{
"Name": "kv-appconfig-demo",
"Rbac": true,
"Uri": "https://kv-appconfig-demo.vault.azure.localhost.localstack.cloud:4566"
}

Writing a secret to an RBAC-enabled vault requires the Key Vault Secrets Officer role on the vault. Resolve the object ID of the principal you are signed in as, then grant it that role:

Terminal window
KEY_VAULT_ID=$(az keyvault show \
--name kv-appconfig-demo \
--resource-group rg-appconfig-demo \
--query id \
--output tsv)
CALLER_OBJECT_ID=$(az ad signed-in-user show --query id --output tsv 2>/dev/null \
|| az ad sp show --id "$(az account show --query user.name --output tsv)" --query id --output tsv)
az role assignment create \
--assignee-object-id "$CALLER_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets Officer" \
--scope "$KEY_VAULT_ID" \
--query "roleDefinitionName" \
--output tsv
Output
Key Vault Secrets Officer

Write the secret and capture the identifier it returns:

Terminal window
SECRET_ID=$(az keyvault secret set \
--vault-name kv-appconfig-demo \
--name db-password \
--value "P@ssw0rd-from-key-vault" \
--query "id" \
--output tsv)
echo "$SECRET_ID"
Output
https://kv-appconfig-demo.vault.azure.localhost.localstack.cloud:4566/secrets/db-password/587b6bfb287d49a9bc498eca45f61cd0

Create the reference using that secret identifier:

Terminal window
az appconfig kv set-keyvault \
--name appconfig-demo-localstack \
--key "app/secrets/db-password" \
--label production \
--secret-identifier "$SECRET_ID" \
--yes
Output
{
"contentType": "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8",
"etag": "2c01f41547944aecad34f963c5477324",
"key": "app/secrets/db-password",
"label": "production",
"lastModified": "2026-09-16T14:11:57.108598+00:00",
"locked": false,
"tags": {},
"value": "{\"uri\": \"https://kv-appconfig-demo.vault.azure.localhost.localstack.cloud:4566/secrets/db-password/587b6bfb287d49a9bc498eca45f61cd0\"}"
}

A Key Vault reference is an ordinary key-value whose content type is application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8 and whose value is a JSON document holding the secret identifier. Reading it back plainly returns that identifier, not the secret. Pass --resolve-keyvault to have the CLI fetch the secret from Key Vault with your credentials:

Terminal window
az appconfig kv list \
--name appconfig-demo-localstack \
--key "app/secrets/db-password" \
--label production \
--resolve-keyvault \
--all \
--output table
Output
CONTENT TYPE KEY VALUE LAST MODIFIED TAGS LABEL LOCKED
------------------------------------------------------------------ ----------------------- ----------------------- -------------------- ------ ---------- --------
application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8 app/secrets/db-password P@ssw0rd-from-key-vault 2026-09-16T14:11:57Z {} production False

An application should read configuration with its own identity rather than an access key. Create a user-assigned managed identity and capture its principal ID, which is what role assignments are evaluated against:

Terminal window
az identity create \
--name id-appconfig-demo \
--resource-group rg-appconfig-demo \
--location westeurope \
--query "{Name:name, ClientId:clientId, PrincipalId:principalId}"
Output
{
"ClientId": "5886b11c-c8aa-41c7-b5e4-28a74405fd3e",
"Name": "id-appconfig-demo",
"PrincipalId": "6828983e-8850-42cc-904a-c1e04dce057f"
}
Terminal window
IDENTITY_PRINCIPAL_ID=$(az identity show \
--name id-appconfig-demo \
--resource-group rg-appconfig-demo \
--query principalId \
--output tsv)
STORE_ID=$(az appconfig show \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--query id \
--output tsv)

Grant the identity the App Configuration Data Reader role at the scope of the store. The assignment targets the principal ID, not the client ID:

Terminal window
az role assignment create \
--assignee-object-id "$IDENTITY_PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "App Configuration Data Reader" \
--scope "$STORE_ID" \
--query "roleDefinitionName" \
--output tsv
Output
App Configuration Data Reader

Verify the assignment:

Terminal window
az role assignment list \
--assignee "$IDENTITY_PRINCIPAL_ID" \
--scope "$STORE_ID" \
--query "[].{Role:roleDefinitionName, PrincipalType:principalType}" \
--output table
Output
Role PrincipalType
----------------------------- ----------------
App Configuration Data Reader ServicePrincipal

An application running in Azure obtains a token for this identity from the instance metadata service, and that token carries the principal ID above.

By default LocalStack stores role assignments without enforcing them, so every data-plane request succeeds. Enforcement is enabled by an environment variable read once at startup, so it requires restarting the emulator, which clears the in-memory state created so far. The sequence below is therefore self-contained: it starts from a fresh emulator and creates its own resource group and store.

Restart the emulator with enforcement enabled, then re-enable interception:

Terminal window
localstack stop
IMAGE_NAME=localstack/localstack-azure localstack start -d -e LS_AZURE_ENFORCE_RBAC=1
lstk az start-interception

Create a store and seed a key-value using an access key, which is never subject to RBAC:

Terminal window
az group create --name rg-appconfig-rbac-demo --location westeurope --output none
az appconfig create \
--name appconfig-rbac-localstack \
--resource-group rg-appconfig-rbac-demo \
--location westeurope \
--sku Standard \
--output none
az appconfig kv set \
--name appconfig-rbac-localstack \
--key "app/settings/color" \
--value blue \
--auth-mode key \
--yes \
--query "value"
Output
"blue"

Capture the store’s identifiers, then sign in as a second service principal that holds no administrative role. The Azure CLI cannot acquire a managed identity token outside Azure compute, so a plain service principal stands in for the workload here:

Terminal window
STORE_ID=$(az appconfig show \
--name appconfig-rbac-localstack \
--resource-group rg-appconfig-rbac-demo \
--query id \
--output tsv)
STORE_ENDPOINT=$(az appconfig show \
--name appconfig-rbac-localstack \
--resource-group rg-appconfig-rbac-demo \
--query endpoint \
--output tsv)
az login --service-principal -u appcs-workload -p any-pass --tenant any-tenant --output none

Read a key-value through the data-plane endpoint with --auth-mode login. A caller holding no data role is refused:

Terminal window
az appconfig kv show \
--endpoint "$STORE_ENDPOINT" \
--key "app/settings/color" \
--auth-mode login
Output
ERROR: Failed to retrieve key-values from config store. Operation returned an invalid status 'FORBIDDEN'
Content: {"type": "https://azconfig.io/errors/forbidden", "title": "Forbidden.", "detail": "The principal 'b70f3654-c056-4bfa-8c6d-620e27ff4a92' does not have the required permission 'Microsoft.AppConfiguration/configurationStores/keyValues/read' on '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-appconfig-rbac-demo/providers/Microsoft.AppConfiguration/configurationStores/appconfig-rbac-localstack'.", "status": 403}

The denial names the principal, the permission it lacked, and the scope it was evaluated at. Capture that principal’s object ID from the oid claim of its own access token, which is what a role assignment must target:

Terminal window
WORKLOAD_OBJECT_ID=$(az account get-access-token --query accessToken --output tsv \
| jq -Rr 'split(".")[1] | @base64d | fromjson | .oid')
echo "$WORKLOAD_OBJECT_ID"
Output
b70f3654-c056-4bfa-8c6d-620e27ff4a92

Sign back in as the administrative principal and grant the workload the App Configuration Data Reader role:

Terminal window
az login --service-principal -u any-app -p any-pass --tenant any-tenant --output none
az role assignment create \
--assignee-object-id "$WORKLOAD_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "App Configuration Data Reader" \
--scope "$STORE_ID" \
--query "roleDefinitionName" \
--output tsv
Output
App Configuration Data Reader

Sign back in as the workload. The read now succeeds:

Terminal window
az login --service-principal -u appcs-workload -p any-pass --tenant any-tenant --output none
az appconfig kv show \
--endpoint "$STORE_ENDPOINT" \
--key "app/settings/color" \
--query "value" \
--output tsv \
--auth-mode login
Output
blue

A write is still refused, because App Configuration Data Reader grants no write permission:

Terminal window
az appconfig kv set \
--endpoint "$STORE_ENDPOINT" \
--key "app/settings/color" \
--value red \
--auth-mode login \
--yes
Output
ERROR: Failed to set the key-value due to an exception: Operation returned an invalid status 'FORBIDDEN'
Content: {"type": "https://azconfig.io/errors/forbidden", "title": "Forbidden.", "detail": "The principal 'b70f3654-c056-4bfa-8c6d-620e27ff4a92' does not have the required permission 'Microsoft.AppConfiguration/configurationStores/keyValues/write' on '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-appconfig-rbac-demo/providers/Microsoft.AppConfiguration/configurationStores/appconfig-rbac-localstack'.", "status": 403}

Assigning App Configuration Data Owner instead allows both reads and writes:

Terminal window
az login --service-principal -u any-app -p any-pass --tenant any-tenant --output none
az role assignment create \
--assignee-object-id "$WORKLOAD_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "App Configuration Data Owner" \
--scope "$STORE_ID" \
--query "roleDefinitionName" \
--output tsv
az login --service-principal -u appcs-workload -p any-pass --tenant any-tenant --output none
az appconfig kv set \
--endpoint "$STORE_ENDPOINT" \
--key "app/settings/color" \
--value red \
--auth-mode login \
--yes \
--query "value"
Output
"red"

Access keys are not affected by role assignments. A client authenticating with a connection string is authenticated by the key it signed with and is never evaluated against RBAC, exactly as in Azure.

Sign back in as the administrative principal before cleaning up, because the workload principal is not authorized to delete these resources:

Terminal window
az login --service-principal -u any-app -p any-pass --tenant any-tenant --output none
az appconfig delete \
--name appconfig-rbac-localstack \
--resource-group rg-appconfig-rbac-demo \
--yes
az appconfig purge --name appconfig-rbac-localstack --yes
az group delete --name rg-appconfig-rbac-demo --yes

Delete an individual key-value:

Terminal window
az appconfig kv delete \
--name appconfig-demo-localstack \
--key "app/settings/size" \
--label production \
--yes \
--query "[].key"
Output
[
"app/settings/size"
]

Delete the store. On the Standard tier this is a soft delete:

Terminal window
az appconfig delete \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--yes

The store no longer appears in az appconfig list, but it is retained until its scheduled purge date:

Terminal window
az appconfig show-deleted --name appconfig-demo-localstack
Output
{
"configurationStoreId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-appconfig-demo/providers/Microsoft.AppConfiguration/configurationStores/appconfig-demo-localstack",
"deletionDate": "2026-09-16T14:12:02.287739+00:00",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.AppConfiguration/locations/westeurope/deletedConfigurationStores/appconfig-demo-localstack",
"location": "westeurope",
"name": "appconfig-demo-localstack",
"purgeProtectionEnabled": false,
"scheduledPurgeDate": "2026-09-23T14:12:02.287739+00:00",
"systemData": null,
"tags": {
"environment": "demo",
"owner": "platform"
},
"type": "Microsoft.AppConfiguration/deletedConfigurationStores"
}

Restore it with az appconfig recover, which takes no resource group because a deleted store is addressed at subscription scope:

Terminal window
az appconfig recover --name appconfig-demo-localstack --yes

To remove a store permanently, delete it and then purge it. A soft-deleted store keeps its globally unique name reserved, so purging is what allows the same name to be reused:

Terminal window
az appconfig delete \
--name appconfig-demo-localstack \
--resource-group rg-appconfig-demo \
--yes
az appconfig purge --name appconfig-demo-localstack --yes

Finally, remove the resource group and confirm it is gone:

Terminal window
az group delete --name rg-appconfig-demo --yes
az group exists --name rg-appconfig-demo
Output
false

The App Configuration emulator supports the following features:

  • Configuration stores: Create, get, list by subscription and by resource group, update, and delete stores, across the Free, Developer, Standard, and Premium SKUs.
  • Soft delete and purge: The full lifecycle of deleting, listing and showing deleted stores, recovering them, and purging them, including purge protection and a configurable retention period.
  • Access keys: List the four access keys of a store, regenerate any of them individually, and use the generated connection strings.
  • Key-values: Set, show, list, and delete key-values, with content types, tags, and labels.
  • Filtering: Filter listings by key and label, using exact values, star wildcards, or comma-separated lists, and project fields with --fields.
  • Revisions: Every write is retained as a revision, listed newest first and trimmed according to the store’s retention period.
  • Point-in-time reads: Read key-values, keys, labels, and revisions as they stood at an earlier instant with --datetime.
  • Snapshots: Create snapshots over a key and label filter, show and list them, archive and recover them, and list a snapshot’s frozen contents.
  • Locks: Lock a key-value to make it read-only and unlock it to restore writes.
  • Feature flags: Set, show, list, enable, and disable feature flags, including percentage and other filters, stored as key-values under the .appconfig.featureflag/ prefix.
  • Key Vault references: Store references to Key Vault secrets with the standard reference content type, resolved client-side by the CLI or a configuration provider.
  • Import and export: Move key-values between a store and a file with az appconfig kv import and az appconfig kv export.
  • Data-plane RBAC: Evaluate App Configuration Data Reader and App Configuration Data Owner against bearer callers, including managed identities, when enforcement is enabled.
  • Access key authentication: Requests signed with an access key are accepted, and a read-only key is refused write operations.
  • Replicas: Create, get, list, and delete replicas. A replica endpoint serves the contents of its parent store.
  • Private endpoints: The full Azure Resource Manager lifecycle of private endpoint connections, driven by Microsoft.Network, including auto-approval, rejection, and cascading deletion.
  • Pagination and conditional requests: Cursor-based pagination over large listings, ETag preconditions on individual key-values, conditional list requests, and Range requests over revisions.
  • Data-plane RBAC is not enforced by default: Role assignments are stored but every request succeeds. Set LS_AZURE_ENFORCE_RBAC when starting the emulator to enforce them. See Role Assignment: Enabling RBAC enforcement.
  • Access key signatures are not verified: The credential identifier in a request is resolved and checked, but the signature itself, the date skew, and the content hash are not. The emulator accepts requests that Azure would reject, never the reverse.
  • Private endpoints do not isolate traffic: Connections are created, approved, rejected, and reported on the store, but no request is blocked and publicNetworkAccess is left as the client set it. The per-tier limit on the number of connections is not enforced.
  • Replicas do not replicate: A replica endpoint serves the same single store, so there is no replication lag, no regional failover, and no region-specific outage.
  • Snapshot behavior differs in several details: Azure Resource Manager exposes only snapshot creation and retrieval, so listing, archiving, and recovering are available on the data plane alone. A snapshot filter is returned without the empty tags field that Azure includes. Snapshot creation is synchronous, so a snapshot is already ready in the response rather than reaching that state asynchronously, and the reported size is approximate.
  • Customer-managed keys are cosmetic: The encryption.keyVaultProperties settings are stored and returned, but no data is encrypted with them.
  • Not implemented: Event Grid filters, private endpoint connection proxies, network security perimeter configurations, and request throttling.
  • The api-version parameter is required: Data-plane requests that omit it are rejected, while Azure serves them. This makes the emulator stricter than the service it emulates.
  • Terraform cannot manage key-values: The azurerm_app_configuration_key and azurerm_app_configuration_feature resources require an App Configuration domain suffix that the Azure Resource Manager metadata document does not carry, so the provider cannot resolve it for any non-public environment. Use the Azure CLI or Bicep to manage key-values. The azurerm_app_configuration store resource itself works.
  • No data persistence across restarts: Store, key-value, revision, and snapshot data is held in memory and is lost when the LocalStack emulator is stopped or restarted.

The following samples demonstrate how to use Azure App Configuration with LocalStack for Azure:

OperationImplemented
Page 1 of 0
Was this page helpful?