Skip to content
Docs Try Aspire
Docs Try

Customize Azure resources

Azure logo

When working with Azure integrations in .NET Aspire, you often need to customize the provisioned infrastructure beyond the default settings. The same customization patterns apply across Azure hosting integrations such as Storage, Service Bus, Key Vault, user-assigned identities, Azure Container Apps, and Azure App Service. This page documents all customization APIs supported across both C# and TypeScript AppHost projects.

For target-specific generated resource customization, see Deploy to Azure Container Apps and Deploy to Azure App Service.

Use AsExisting, RunAsExisting, and PublishAsExisting when you want Aspire to reference an Azure resource that already exists instead of provisioning a new one.

C# — AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var existingBusName = builder.AddParameter("existingServiceBusName");
var existingBusResourceGroup = builder.AddParameter("existingServiceBusResourceGroup");
var serviceBus = builder.AddAzureServiceBus("messaging")
.PublishAsExisting(existingBusName, existingBusResourceGroup);
builder.Build().Run();

Use RunAsExisting when only local run mode should use the existing resource, PublishAsExisting when only deployed Azure environments should use it, and AsExisting when both modes should point at the same Azure resource.

Aspire automatically assigns Azure RBAC roles based on how resources reference one another. When you need to opt out of those defaults before applying different permissions, use ClearDefaultRoleAssignments.

C# — AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureServiceBus("messaging")
.ClearDefaultRoleAssignments();
builder.Build().Run();

For built-in and custom RBAC guidance, see Manage Azure role assignments.

Azure resources expose output references for values that Azure assigns during provisioning. Use those references when another resource, deployment step, or app setting needs the resolved Azure value.

C# — AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var identity = builder.AddAzureUserAssignedIdentity("identity");
builder.AddProject<Projects.Api>("api")
.WithEnvironment("IDENTITY_CLIENT_ID", identity.Resource.ClientId);
builder.Build().Run();

Use GetBicepIdentifier() inside ConfigureInfrastructure or infrastructure resolvers when you need a stable identifier for a provisioned Azure construct. Use output references such as ClientId, NameOutputReference, or getOutput("...") when you need Azure-assigned values like a client ID, endpoint, or resource name.

The ConfigureInfrastructure API lets you customize the Azure resources that Aspire generates during provisioning. In C#, it provides a strongly-typed surface over the Azure.Provisioning library, letting you modify any property of the generated Azure resource types before Bicep is emitted.

All Azure hosting resources in Aspire inherit from AzureProvisioningResource, which exposes ConfigureInfrastructure. The callback receives an AzureResourceInfrastructure instance that gives you access to all provisioned constructs for that resource.

In C#, use GetProvisionableResources() with the strongly-typed Azure SDK types (for example StorageAccount, ServiceBusNamespace) to navigate and mutate each construct:

C# — AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var storage = builder.AddAzureStorage("storage");
storage.ConfigureInfrastructure(infra =>
{
var storageAccount = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
storageAccount.Sku = new StorageSku { Name = StorageSkuName.PremiumLRS };
storageAccount.Tags["Environment"] = "Development";
storageAccount.Tags["CostCenter"] = "Engineering";
});
builder.Build().Run();
C# — AppHost.cs (Storage)
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.Sku = new StorageSku { Name = StorageSkuName.StandardGRS };
account.Kind = StorageKind.StorageV2;
});
C# — AppHost.cs (Service Bus)
serviceBus.ConfigureInfrastructure(infra =>
{
var ns = infra.GetProvisionableResources()
.OfType<ServiceBusNamespace>()
.Single();
ns.Sku = new ServiceBusSku
{
Name = ServiceBusSkuName.Premium,
Capacity = 2
};
});
C# — AppHost.cs (Redis)
redis.ConfigureInfrastructure(infra =>
{
var cache = infra.GetProvisionableResources()
.OfType<RedisCache>()
.Single();
cache.Sku = new RedisCacheSku
{
Name = RedisCacheSkuName.Premium,
Family = RedisCacheSkuFamily.P,
Capacity = 1
};
});
C# — AppHost.cs
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.NetworkRuleSet = new StorageAccountNetworkRuleSet
{
DefaultAction = StorageNetworkDefaultAction.Deny,
Bypass = "AzureServices"
};
account.NetworkRuleSet.IpRules.Add(new StorageAccountIPRule
{
IPAddressOrRange = "203.0.113.0/24",
Action = "Allow"
});
});
C# — AppHost.cs
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.EnableHttpsTrafficOnly = true;
account.MinimumTlsVersion = StorageMinimumTlsVersion.Tls1_2;
account.Encryption = new StorageAccountEncryption
{
KeySource = StorageAccountKeySource.MicrosoftStorage,
Services = new StorageAccountEncryptionServices
{
Blob = new StorageEncryptionService { Enabled = true },
File = new StorageEncryptionService { Enabled = true }
}
};
});

When an integration creates multiple resources (for example, a Service Bus namespace and its queues), you can customize each one inside a single ConfigureInfrastructure call:

C# — AppHost.cs
var servicebus = builder.AddAzureServiceBus("messaging");
var queue = servicebus.AddQueue("orders");
servicebus.ConfigureInfrastructure(infra =>
{
var ns = infra.GetProvisionableResources()
.OfType<ServiceBusNamespace>()
.Single();
ns.Sku = new ServiceBusSku { Name = ServiceBusSkuName.Standard };
var queueResource = infra.GetProvisionableResources()
.OfType<ServiceBusQueue>()
.FirstOrDefault(q => q.Name.Contains("orders"));
if (queueResource != null)
{
queueResource.MaxDeliveryCount = 5;
queueResource.DefaultMessageTimeToLive = TimeSpan.FromHours(24);
}
});

You can inject additional Azure constructs (for example, a private endpoint) into an integration’s generated infrastructure:

C# — AppHost.cs
storage.ConfigureInfrastructure(infra =>
{
var privateEndpoint = new PrivateEndpoint("storagepe")
{
Location = "eastus",
Subnet = new SubnetReference
{
Id = "/subscriptions/.../subnets/mysubnet"
}
};
infra.Add(privateEndpoint);
});

Customize naming and provisioning with an infrastructure resolver

Section titled “Customize naming and provisioning with an infrastructure resolver”

Another way to customize Azure provisioning is to create an InfrastructureResolver. This is a C#-only API that lets you apply organization-wide naming conventions or other centralized provisioning behavior across many Azure resources.

Define a resolver by inheriting from InfrastructureResolver and overriding the relevant virtual members:

C# — AppHost.cs
using Azure.Provisioning;
using Azure.Provisioning.CosmosDB;
using Azure.Provisioning.Primitives;
internal sealed class FixedNameInfrastructureResolver : InfrastructureResolver
{
public override void ResolveProperties(ProvisionableConstruct construct, ProvisioningBuildOptions options)
{
if (construct is CosmosDBAccount account)
{
account.Name = "ContosoCosmosDb";
}
base.ResolveProperties(construct, options);
}
}

Register the resolver in the AppHost:

C# — AppHost.cs
using Aspire.Hosting.Azure;
using Microsoft.Extensions.DependencyInjection;
var builder = DistributedApplication.CreateBuilder(args);
builder.Services.Configure<AzureProvisioningOptions>(options =>
{
options.ProvisioningBuildOptions.InfrastructureResolvers.Add(new FixedNameInfrastructureResolver());
});
builder.Build().Run();

For scenarios requiring full control, you can supply a custom Bicep file and reference it from your AppHost. This approach works in both C# and TypeScript.

Create a Bicep file in your AppHost project:

custom-storage.bicep
@description('Storage account name')
param storageAccountName string
@description('Location')
param location string = resourceGroup().location
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: {
name: 'Premium_LRS'
}
kind: 'BlockBlobStorage'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
}
}
}
output connectionString string = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};...'

Reference it from the AppHost:

C# — AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var storage = builder.AddAzureBicepResource(
name: "storage",
bicepFilePath: "./custom-storage.bicep")
.WithParameter("storageAccountName", "mystorageaccount");
builder.AddProject<Projects.WebApp>("webapp")
.WithReference(storage);
builder.Build().Run();

After customizing with ConfigureInfrastructure, inspect the Bicep that Aspire generates:

  1. Run your AppHost locally.
  2. Open the ./infra directory inside your AppHost project.
  3. Review the generated .bicep files to verify your customizations.