Customize Azure resources
When working with Azure integrations in Aspire, you often need to customize the generated infrastructure beyond the default settings. Aspire provides two main approaches for customizing Azure resources: using the ConfigureInfrastructure API and creating custom Bicep templates.
Azure.Provisioning customization
The ConfigureInfrastructure API provides a strongly-typed, C#-based way to customize Azure resources. This is the recommended approach for most customizations. It uses the Azure.Provisioning library to generate Bicep from C# code.
[!TIP] Azure.Provisioning resources For detailed API documentation and examples, see:
Basic infrastructure customization
All Azure resources in Aspire inherit from AzureProvisioningResource, which exposes the ConfigureInfrastructure method:
var builder = DistributedApplication.CreateBuilder(args);
var storage = builder.AddAzureStorage("storage");
storage.ConfigureInfrastructure(infra =>
{
// Get the storage account resource
var storageAccount = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
// Customize the SKU
storageAccount.Sku = new StorageSku
{
Name = StorageSkuName.PremiumLRS
};
// Add tags
storageAccount.Tags["Environment"] = "Development";
storageAccount.Tags["CostCenter"] = "Engineering";
});
Common customization scenarios
Configure SKUs and tiers
Storage
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.Sku = new StorageSku
{
Name = StorageSkuName.StandardGRS
};
account.Kind = StorageKind.StorageV2;
});
Service Bus
serviceBus.ConfigureInfrastructure(infra =>
{
var ns = infra.GetProvisionableResources()
.OfType<ServiceBusNamespace>()
.Single();
ns.Sku = new ServiceBusSku
{
Name = ServiceBusSkuName.Premium,
Capacity = 2
};
});
Redis
redis.ConfigureInfrastructure(infra =>
{
var cache = infra.GetProvisionableResources()
.OfType<RedisCache>()
.Single();
cache.Sku = new RedisCacheSku
{
Name = RedisCacheSkuName.Premium,
Family = RedisCacheSkuFamily.P,
Capacity = 1
};
});
Configure networking
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
// Enable firewall
account.NetworkRuleSet = new StorageAccountNetworkRuleSet
{
DefaultAction = StorageNetworkDefaultAction.Deny,
Bypass = "AzureServices"
};
// Allow specific IP
account.NetworkRuleSet.IpRules.Add(new StorageAccountIPRule
{
IPAddressOrRange = "203.0.113.0/24",
Action = "Allow"
});
});
Configure security and compliance
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
// Require HTTPS
account.EnableHttpsTrafficOnly = true;
// Configure TLS version
account.MinimumTlsVersion = StorageMinimumTlsVersion.Tls1_2;
// Enable blob encryption
account.Encryption = new StorageAccountEncryption
{
KeySource = StorageAccountKeySource.MicrosoftStorage,
Services = new StorageAccountEncryptionServices
{
Blob = new StorageEncryptionService { Enabled = true },
File = new StorageEncryptionService { Enabled = true }
}
};
});
Working with multiple resources
When an integration creates multiple resources, you can customize each one:
var servicebus = builder.AddAzureServiceBus("messaging");
var queue = servicebus.AddQueue("orders");
servicebus.ConfigureInfrastructure(infra =>
{
// Customize the namespace
var ns = infra.GetProvisionableResources()
.OfType<ServiceBusNamespace>()
.Single();
ns.Sku = new ServiceBusSku { Name = ServiceBusSkuName.Standard };
// Customize the queue
var queueResource = infra.GetProvisionableResources()
.OfType<ServiceBusQueue>()
.FirstOrDefault(q => q.Name.Contains("orders"));
if (queueResource != null)
{
queueResource.MaxDeliveryCount = 5;
queueResource.DefaultMessageTimeToLive = TimeSpan.FromHours(24);
}
});
Adding Azure resources
You can add additional Azure resources to the infrastructure:
storage.ConfigureInfrastructure(infra =>
{
// Add a private endpoint
var privateEndpoint = new PrivateEndpoint("storagepe")
{
Location = "eastus",
Subnet = new SubnetReference
{
Id = "/subscriptions/.../subnets/mysubnet"
}
};
infra.Add(privateEndpoint);
});
Use an infrastructure resolver to customize Azure provisioning options
Another method you can use to customize Azure provisioning is to create an InfrastructureResolver and write code in it to implement your requirements. Then add that class to the configuration options for your AppHost.
A custom infrastructure resolver is a class that inherits from InfrastructureResolver and can override one or more of its virtual members to apply the desired customizations. In the following example, the ResolveProperties method is overridden to set the name of a Cosmos DB resource, but other members can also be overridden depending on your needs.
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);
}
}
Having created that class, add it to the configuration options using code like this in the AppHost:
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();
Custom Bicep templates
For more complex scenarios or when you need full control, you can provide custom Bicep templates.
Using custom Bicep files
Create a custom Bicep file in your AppHost project:
@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 in your AppHost:
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);
Combining approaches
You can combine ConfigureInfrastructure with custom Bicep for maximum flexibility:
// Start with Aspire's default
var storage = builder.AddAzureStorage("storage");
// Customize with ConfigureInfrastructure
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.Sku = new StorageSku { Name = StorageSkuName.PremiumLRS };
});
// Add custom resources via Bicep
var customResource = builder.AddAzureBicepResource(
name: "custom",
bicepFilePath: "./custom-resource.bicep");
Best practices
Use ConfigureInfrastructure for standard customizations
For common property changes like SKUs, tags, and basic configuration, prefer ConfigureInfrastructure:
// ✅ Good - Simple and maintainable
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.Sku = new StorageSku { Name = StorageSkuName.PremiumLRS };
});
Use custom Bicep for complex scenarios
Use custom Bicep when you need:
- Complex networking configurations
- Multiple interconnected resources
- Custom resource types not in Azure.Provisioning
- Advanced policy configurations
Keep infrastructure code close to application code
Define infrastructure customizations in your AppHost alongside resource declarations:
// All storage configuration in one place
var storage = builder.AddAzureStorage("storage")
.ConfigureInfrastructure(infra =>
{
// Customizations here
});
Use configuration for environment-specific values
Don't hard-code environment-specific values:
var skuName = builder.Configuration["Azure:Storage:Sku"] ?? "Standard_LRS";
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.Sku = new StorageSku { Name = skuName };
});
Generated Bicep inspection
After customization, you can inspect the generated Bicep:
- Run your AppHost locally
- Check the
./infradirectory in your AppHost project - Review the generated
.bicepfiles
The generated files reflect all customizations made via ConfigureInfrastructure.
Troubleshooting
Finding resource types
If you're unsure what resource type to use:
storage.ConfigureInfrastructure(infra =>
{
// List all resources
var resources = infra.GetProvisionableResources();
foreach (var resource in resources)
{
Console.WriteLine($"Type: {resource.GetType().Name}");
}
});
Property not available
If a property isn't available in the Azure.Provisioning types, you may need to:
- Update to the latest Aspire version
- Use custom Bicep for that specific property
- File an issue on the Aspire GitHub repository
Validation errors
Azure will validate your configuration during deployment. Common issues:
- Invalid SKU names or combinations
- Location mismatches
- Naming convention violations
- Missing required properties
Tip
Use the Azure Portal's "Export template" feature to see the Bicep for manually configured resources, then adapt it for Aspire.