Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

4. Getting Started

9 min read

A minimal, runnable store — three ways, because interviews and real jobs use all three.

Scope discipline. Everything on this page is deliberately throwaway: hard-coded names, no variables, no remote state, no pipeline, Free tier, public network access. If a snippet here would embarrass you in a pull request, that's intentional — the production-shaped version lives in Deployment.

The task, the same in all three paths: create a store, put two key-values in it with different labels, add a feature flag, read it back.

Create a throwaway resource group first and delete that at the end. It's the cleanest teardown Azure gives you — one command removes every resource inside it — and it's a genuine advantage over AWS worth pointing out once.

Portal, CLI, and Terraform converging on the same store with two labelled key-values and a feature flag

Before you start

  • An Azure subscription and either the portal or a logged-in CLI (az login).
  • A data-plane role on yourself. This is the step people skip. Creating the store makes you Owner on the resource; it does not let you read or write key-values. Assign yourself App Configuration Data Owner — the command is in the CLI path below and it is the single most useful thing on this page.
  • Store names are globally unique (they become a DNS label), so substitute your own suffix everywhere you see demo01 below.

Path 1 — Azure Portal

The fast way to build intuition. Not repeatable, and the portal's navigation changes often, so this is kept short and blade-based rather than a chain of exact button labels.

  • Create a resource → search "App Configuration" → Create. Pick your throwaway resource group, a region, a globally unique name, and the Free tier. Create.
  • On the new resource, open the Access control (IAM) blade and assign yourself App Configuration Data Owner. Do this before anything else or the next blade will refuse you.
  • Open the Configuration explorer blade → Create → Key-value. Add Api:Timeout = 30 with no label, then create it again as Api:Timeout = 60 with label prod. You now have two rows with the same key — which is the whole point, and worth pausing on.
  • Open the Feature manager blade → Create. Name it Beta, leave it disabled, then enable it and add a percentage filter set to 50. Notice that the feature flag also appears in Configuration explorer as a key under .appconfig.featureflag/ — the Feature manager is a view, not a separate store.
  • Open the Access settings blade to see the endpoint and the access keys. Note that the keys are visible to anyone with Contributor on the resource; that's the plane leak described in Architecture.

Path 2 — Azure CLI

Copy-pasteable, repeatable, and the version to actually learn.

# --- variables -------------------------------------------------------------
RG=rg-appconfig-demo
LOC=uksouth
STORE=appcs-demo01          # must be globally unique — change the suffix

# --- create the resource group and the store -------------------------------
az group create -n $RG -l $LOC

az appconfig create \
  -n $STORE -g $RG -l $LOC \
  --sku Free

# --- grant YOURSELF data-plane access (the step everyone forgets) ----------
STORE_ID=$(az appconfig show -n $STORE -g $RG --query id -o tsv)
ME=$(az ad signed-in-user show --query id -o tsv)

az role assignment create \
  --assignee-object-id "$ME" --assignee-principal-type User \
  --role "App Configuration Data Owner" \
  --scope "$STORE_ID"
# Role assignments take a few seconds to propagate. If the next command
# returns 403, wait and retry rather than debugging it.

# --- write key-values, using your identity rather than an access key -------
az appconfig kv set -n $STORE --auth-mode login --yes \
  --key "Api:Timeout" --value "30"

az appconfig kv set -n $STORE --auth-mode login --yes \
  --key "Api:Timeout" --value "60" --label prod

az appconfig kv set -n $STORE --auth-mode login --yes \
  --key "Api:BaseUrl" --value "https://api.example.com" --label prod \
  --content-type "text/plain"

# a sentinel key — the pattern explained in Architecture
az appconfig kv set -n $STORE --auth-mode login --yes \
  --key "Sentinel" --value "1" --label prod

# --- a feature flag --------------------------------------------------------
az appconfig feature set -n $STORE --auth-mode login --yes \
  --feature Beta --label prod

az appconfig feature enable -n $STORE --auth-mode login --yes \
  --feature Beta --label prod

# --- read it back ----------------------------------------------------------
# everything, so you can see both labels side by side
az appconfig kv list -n $STORE --auth-mode login --fields key label value -o table

# what a 'prod' client would actually see: no-label defaults, then prod overrides
az appconfig kv list -n $STORE --auth-mode login --label '\0' --fields key value -o table
az appconfig kv list -n $STORE --auth-mode login --label prod  --fields key value -o table

# the feature flag as it really is — a key-value with a reserved prefix
az appconfig kv show -n $STORE --auth-mode login \
  --key ".appconfig.featureflag/Beta" --label prod

Three things in there are worth more than the rest:

  • --auth-mode login makes the CLI use your Entra identity instead of silently fetching an access key. Get in the habit; it's the only mode that works once local auth is disabled, and it's why the role assignment above was necessary.
  • --label '\0' is how the CLI expresses "the null label". It is unintuitive, it differs from an omitted --label (which means "any label"), and it is the source of a great deal of confusion.
  • The feature flag is just a key-value. Reading .appconfig.featureflag/Beta directly is the quickest way to internalise that the Feature manager blade is a UI over the same store.

PowerShell note

There is an Az.AppConfiguration module for the control plane (creating and configuring the store), but the data plane — key-values and feature flags — is not its strength; the az appconfig kv commands and the SDKs are the practical path. Since App Configuration's consumers are usually applications rather than Windows administrators, this page doesn't carry a full PowerShell translation. If your shop is PowerShell-first, use New-AzAppConfigurationStore for provisioning and the .NET SDK or az for the data.

Path 3 — Terraform (minimal)

The smallest declarative version. The parameterised, remote-state, CI-wired version is in Deployment.

terraform {
  required_providers {
    azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
  }
}

provider "azurerm" {
  features {}
}

data "azurerm_client_config" "current" {}

resource "azurerm_resource_group" "demo" {
  name     = "rg-appconfig-demo"
  location = "uksouth"
}

resource "azurerm_app_configuration" "demo" {
  name                = "appcs-demo01" # globally unique — change the suffix
  resource_group_name = azurerm_resource_group.demo.name
  location            = azurerm_resource_group.demo.location
  sku                 = "free"
}

# The role assignment is NOT optional. Terraform writes key-values through the
# data plane, and the identity running `apply` has no data-plane permission just
# because it created the resource.
resource "azurerm_role_assignment" "tf_data_owner" {
  scope                = azurerm_app_configuration.demo.id
  role_definition_name = "App Configuration Data Owner"
  principal_id         = data.azurerm_client_config.current.object_id
}

resource "azurerm_app_configuration_key" "timeout_default" {
  configuration_store_id = azurerm_app_configuration.demo.id
  key                    = "Api:Timeout"
  value                  = "30"

  depends_on = [azurerm_role_assignment.tf_data_owner]
}

resource "azurerm_app_configuration_key" "timeout_prod" {
  configuration_store_id = azurerm_app_configuration.demo.id
  key                    = "Api:Timeout"
  label                  = "prod"
  value                  = "60"

  depends_on = [azurerm_role_assignment.tf_data_owner]
}

resource "azurerm_app_configuration_feature" "beta" {
  configuration_store_id = azurerm_app_configuration.demo.id
  name                   = "Beta"
  label                  = "prod"
  enabled                = true

  depends_on = [azurerm_role_assignment.tf_data_owner]
}
terraform init
terraform plan
terraform apply

The depends_on blocks are the whole lesson of this snippet. Terraform can't infer that writing a key-value requires the role assignment to exist first, because the key resource references the store, not the assignment. Without them you get a 403 on the very first apply and a successful one on the second — a flaky pipeline whose cause looks like a race condition and is actually a missing dependency edge. Even with them, RBAC propagation is eventually consistent, so an occasional retry is normal; Deployment covers the sturdier patterns (assign the role in a separate earlier stage, or grant it to the pipeline identity once at the resource-group scope, out of band).

Read it from an application

Two keys and a flag are only interesting once something consumes them. The minimal .NET version:

dotnet new console -n AppConfigDemo && cd AppConfigDemo
dotnet add package Microsoft.Extensions.Configuration.AzureAppConfiguration
dotnet add package Azure.Identity
export APPCONFIG_ENDPOINT="https://appcs-demo01.azconfig.io"
using Azure.Identity;
using Microsoft.Extensions.Configuration;

var endpoint = Environment.GetEnvironmentVariable("APPCONFIG_ENDPOINT")!;

var config = new ConfigurationBuilder()
    .AddAzureAppConfiguration(options =>
    {
        options.Connect(new Uri(endpoint), new DefaultAzureCredential())
               .Select("*", LabelFilter.Null)   // shared defaults first
               .Select("*", "prod");            // prod overrides win
    })
    .Build();

Console.WriteLine($"Api:Timeout = {config["Api:Timeout"]}");   // 60 — prod won
Console.WriteLine($"Api:BaseUrl = {config["Api:BaseUrl"]}");
dotnet run

The endpoint comes from an environment variable, not from the store — there is always exactly one bootstrap value you cannot keep in App Configuration. DefaultAzureCredential picks up your az login session locally and a managed identity when the same code runs in Azure, which is why nothing here holds a key. Swap the two Select calls around and watch the answer change to 30; that one-line experiment teaches the label model better than any paragraph.

Refresh, sentinel keys, feature-flag evaluation, and Key Vault references are deliberately omitted here — they are in Architecture and Integrations.

Teardown

Cleanup note: do this now, not later. Half of all surprise cloud bills come from forgotten demo resources — and in this case a forgotten store also holds a globally unique name you might want back.

az group delete -n rg-appconfig-demo --yes --no-wait

Or, if you used Terraform:

terraform destroy

Two things az group delete does not fully finish for you:

  • The store is soft-deleted, not gone. It keeps its globally unique name for the retention period, so recreating appcs-demo01 will fail until it's recovered or purged. On the Free tier the retention is minimal-to-absent, but get in the habit of checking:

    az appconfig list-deleted -o table
    az appconfig purge -n appcs-demo01 --yes   # only if purge protection is off
  • The role assignment you created on yourself was scoped to the store, so it disappears with the store. Role assignments scoped above the store — at the resource group or subscription — survive and are worth auditing occasionally.


Next: Deployment →

← Back to the Azure App Configuration overview · ← Previous: Architecture