Generate App Configuration stores with feature flags and dynamic configuration
You are an Azure App Configuration expert. Generate production-ready configuration management setups with feature flags, dynamic refresh, and environment-based configuration.
Determine from user input or $ARGUMENTS:
resource appConfig 'Microsoft.AppConfiguration/configurationStores@2023-03-01' = {
name: 'appcs-${appName}-${environment}'
location: location
sku: {
name: 'standard' // free or standard
}
identity: {
type: 'SystemAssigned'
}
properties: {
disableLocalAuth: true // Force Entra ID auth
enablePurgeProtection: true // Prevent accidental deletion
softDeleteRetentionInDays: 7
publicNetworkAccess: 'Disabled' // Use private endpoints
encryption: {
keyVaultProperties: {
keyIdentifier: customerManagedKey.properties.keyUriWithVersion
identityClientId: managedIdentity.properties.clientId
}
}
dataPlaneProxy: {
privateLinkDelegation: 'Enabled'
}
}
tags: commonTags
}
// Private endpoint
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-09-01' = {
name: 'pe-appcs-${appName}'
location: location
properties: {
subnet: {
id: privateEndpointSubnet.id
}
privateLinkServiceConnections: [
{
name: 'appcs-connection'
properties: {
privateLinkServiceId: appConfig.id
groupIds: ['configurationStores']
}
}
]
}
}
// Geo-replication (Standard tier)
resource replica 'Microsoft.AppConfiguration/configurationStores/replicas@2023-03-01' = {
parent: appConfig
name: 'westus-replica'
location: 'westus2'
}
// RBAC role assignments
resource dataReaderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(appConfig.id, appServiceIdentity, 'appconfig-data-reader')
scope: appConfig
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'516239f1-63e1-4d78-a4de-a74fb236a071' // App Configuration Data Reader
)
principalId: appServiceIdentity
principalType: 'ServicePrincipal'
}
}
// Application settings with labels for environments
resource configAppName 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: 'App:Settings:AppName'
properties: {
value: appName
contentType: 'text/plain'
tags: { component: 'core' }
}
}
// Environment-specific with labels
resource configDbConnectionDev 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: 'App:Database:ConnectionString$dev' // $ separator for label
properties: {
value: 'Server=dev-server;Database=myapp;'
contentType: 'text/plain'
}
}
resource configDbConnectionProd 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: 'App:Database:ConnectionString$production'
properties: {
value: 'Server=prod-server;Database=myapp;'
contentType: 'text/plain'
}
}
// Key Vault reference (secrets stored in Key Vault, referenced from App Config)
resource configDbPassword 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: 'App:Database:Password$production'
properties: {
value: json('{"uri":"https://kv-${appName}.vault.azure.net/secrets/db-password"}')
contentType: 'application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8'
}
}
// JSON configuration value
resource configFeatureSettings 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: 'App:Caching:Settings'
properties: {
value: json('''{
"enabled": true,
"ttlSeconds": 300,
"maxItems": 1000,
"provider": "redis"
}''')
contentType: 'application/json'
}
}
// Sentinel key for configuration refresh
resource sentinelKey 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: 'App:Settings:Sentinel'
properties: {
value: '1' // Update this value to trigger refresh in all connected apps
contentType: 'text/plain'
}
}
// Simple boolean feature flag
resource featureBetaDashboard 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: '.appconfig.featureflag~2FBetaDashboard' // ~2F is URL-encoded /
properties: {
value: json('''{
"id": "BetaDashboard",
"description": "Enable the new beta dashboard experience",
"enabled": false,
"conditions": {
"client_filters": []
}
}''')
contentType: 'application/vnd.microsoft.appconfig.ff+json;charset=utf-8'
}
}
// Percentage-based rollout
resource featureNewCheckout 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: '.appconfig.featureflag~2FNewCheckout'
properties: {
value: json('''{
"id": "NewCheckout",
"description": "New checkout flow - gradual rollout",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "Microsoft.Percentage",
"parameters": {
"Value": 25
}
}
]
}
}''')
contentType: 'application/vnd.microsoft.appconfig.ff+json;charset=utf-8'
}
}
// Targeting filter (specific users/groups)
resource featurePremiumFeatures 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: '.appconfig.featureflag~2FPremiumFeatures'
properties: {
value: json('''{
"id": "PremiumFeatures",
"description": "Premium features for targeted users and groups",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "Microsoft.Targeting",
"parameters": {
"Audience": {
"Users": [
"[email protected]",
"[email protected]"
],
"Groups": [
{
"Name": "BetaTesters",
"RolloutPercentage": 100
},
{
"Name": "InternalUsers",
"RolloutPercentage": 50
}
],
"DefaultRolloutPercentage": 10,
"Exclusion": {
"Users": ["[email protected]"],
"Groups": ["ExcludedGroup"]
}
}
}
}
]
}
}''')
contentType: 'application/vnd.microsoft.appconfig.ff+json;charset=utf-8'
}
}
// Time window filter
resource featureHolidaySale 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: '.appconfig.featureflag~2FHolidaySale'
properties: {
value: json('''{
"id": "HolidaySale",
"description": "Holiday sale banner and pricing",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "Microsoft.TimeWindow",
"parameters": {
"Start": "2024-12-20T00:00:00Z",
"End": "2025-01-02T23:59:59Z"
}
}
]
}
}''')
contentType: 'application/vnd.microsoft.appconfig.ff+json;charset=utf-8'
}
}
// Custom filter (evaluate in application code)
resource featureCustomFilter 'Microsoft.AppConfiguration/configurationStores/keyValues@2023-03-01' = {
parent: appConfig
name: '.appconfig.featureflag~2FGeoRestricted'
properties: {
value: json('''{
"id": "GeoRestricted",
"description": "Feature available in specific regions",
"enabled": true,
"conditions": {
"client_filters": [
{
"name": "GeoFilter",
"parameters": {
"AllowedCountries": ["US", "CA", "UK"]
}
}
]
}
}''')
contentType: 'application/vnd.microsoft.appconfig.ff+json;charset=utf-8'
}
}
ASP.NET Core with dynamic refresh:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Connect to App Configuration with managed identity
builder.Configuration.AddAzureAppConfiguration(options =>
{
options.Connect(
new Uri("https://appcs-myapp-prod.azconfig.io"),
new DefaultAzureCredential())
// Load all keys with specific label
.Select(KeyFilter.Any, "production")
// Load keys with no label as fallback
.Select(KeyFilter.Any, LabelFilter.Null)
// Configure Key Vault reference resolution
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
})
// Configure feature flags
.UseFeatureFlags(featureOptions =>
{
featureOptions.Label = "production";
featureOptions.CacheExpirationInterval = TimeSpan.FromMinutes(5);
})
// Configure dynamic refresh
.ConfigureRefresh(refresh =>
{
refresh.Register("App:Settings:Sentinel", "production", refreshAll: true)
.SetCacheExpiration(TimeSpan.FromSeconds(30));
});
});
// Add Azure App Configuration middleware for refresh
builder.Services.AddAzureAppConfiguration();
// Add feature management
builder.Services.AddFeatureManagement()
.AddFeatureFilter<PercentageFilter>()
.AddFeatureFilter<TimeWindowFilter>()
.AddFeatureFilter<TargetingFilter>();
// Add targeting context accessor
builder.Services.AddSingleton<ITargetingContextAccessor, HttpContextTargetingContextAccessor>();
var app = builder.Build();
// Use App Configuration middleware (must be before routing)
app.UseAzureAppConfiguration();
app.MapGet("/api/settings", (IConfiguration config) =>
{
return new
{
AppName = config["App:Settings:AppName"],
CacheSettings = config.GetSection("App:Caching:Settings").Get<CacheSettings>()
};
});
app.MapGet("/api/features", async (IFeatureManager featureManager) =>
{
return new
{
BetaDashboard = await featureManager.IsEnabledAsync("BetaDashboard"),
NewCheckout = await featureManager.IsEnabledAsync("NewCheckout"),
PremiumFeatures = await featureManager.IsEnabledAsync("PremiumFeatures")
};
});
app.Run();
// Custom targeting context accessor
public class HttpContextTargetingContextAccessor : ITargetingContextAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ValueTask<TargetingContext> GetContextAsync()
{
var httpContext = _httpContextAccessor.HttpContext;
var user = httpContext?.User;
return new ValueTask<TargetingContext>(new TargetingContext
{
UserId = user?.FindFirst("oid")?.Value ?? "anonymous",
Groups = user?.FindAll("groups")?.Select(c => c.Value).ToList()
?? new List<string>()
});
}
}
// Using feature flags in controllers
[ApiController]
[Route("api/[controller]")]
public class DashboardController : ControllerBase
{
private readonly IFeatureManager _featureManager;
public DashboardController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
[HttpGet]
public async Task<IActionResult> GetDashboard()
{
if (await _featureManager.IsEnabledAsync("BetaDashboard"))
{
return Ok(new { version = "beta", widgets = GetBetaWidgets() });
}
return Ok(new { version = "stable", widgets = GetStableWidgets() });
}
}
// Feature gate attribute (entire action behind feature flag)
[FeatureGate("PremiumFeatures")]
[HttpGet("premium")]
public IActionResult GetPremiumData()
{
return Ok(new { data = "premium content" });
}
Push-based refresh with Event Grid:
// Azure Function to handle App Configuration change events
[Function("AppConfigChanged")]
public async Task Run(
[EventGridTrigger] CloudEvent cloudEvent,
[FromServices] IConfigurationRefresherProvider refresherProvider)
{
_logger.LogInformation("App Configuration changed: {Type}", cloudEvent.Type);
// Trigger refresh in all connected configuration providers
foreach (var refresher in refresherProvider.Refreshers)
{
refresher.ProcessPushNotification(cloudEvent, TimeSpan.FromSeconds(0));
}
}
# application.yml