What "hybrid" means
Yes, you can absolutely use Azure-hosted databases and blob storage while developing with .NET Aspire locally. It is one of the most common and recommended patterns for cloud-native development - and once you know which API to reach for, the configuration is short.
The correct hybrid setup is straightforward:
- Redis runs locally in a Docker container started by Aspire.
- Azure SQL and Azure Storage already exist in your subscription.
- Aspire connects to them via real connection strings injected from User Secrets or
appsettings.Development.json.
The benefits compound. Tests run against real Azure behavior, including networking, authentication, and latency. Local setup is faster because there is no need to spin up SQL Server or Azurite. And the transition from aspire run to a real deployment is shorter, because the resources you are talking to are already in the cloud.
The three Aspire patterns
Aspire exposes three different APIs for adding a resource to the AppHost, and choosing the right one is the entire game. Picking the wrong one is the most common cause of "why isn't this working?" questions on Aspire codebases.
| Scenario | Use |
|---|---|
| Local containerized service - Aspire starts the container for you. | AddSqlServer(), AddRedis(), AddPostgres() |
| Existing Azure or external resource - someone else (or some other tool) already provisioned it. | AddConnectionString() |
| Provision Azure resources from inside the AppHost (Aspire owns the infrastructure). | AddAzureSqlServer(), AddAzureStorage() |
For the hybrid setup, the middle row is the one that matters: AddConnectionString(). The Azure SQL database and Azure Storage account already exist - Aspire only needs to learn the connection string and pass it to whichever services declare a reference.
Do not mix patterns. If a resource exists in Azure and you only need to connect to it, use AddConnectionString(). If Aspire is the source of truth for the resource (Aspire provisioned it), use AddAzureSqlServer() or AddAzureStorage(). Pick one mode per resource.
Configuring connection strings
Connection strings can live in either appsettings.Development.json (development only, never committed if it contains secrets) or - far better - in User Secrets, which keeps secrets out of source control entirely.
The strongly preferred location for secrets in development is User Secrets. The CLI initializes a per-project secrets file that lives outside the repo (typically under ~/.microsoft/usersecrets/ on macOS / Linux):
Aspire reads the connection strings out of the standard .NET configuration chain - User Secrets, environment variables, appsettings.*.json, in that order. The application code does not change between the two storage locations.
The AppHost example
The full hybrid AppHost stays remarkably short. One local container (Redis), two connection-string references (Azure SQL, Azure Storage), and two projects that consume them:
Why this works:
AddConnectionString()tells Aspire "this resource already exists - just inject its connection string at startup". No provisioning code, no Azure resource group, noAddAzureSqlServer()..WithReference()wires the connection string into the consuming service's configuration underConnectionStrings:AppDb, the same shape the service would see in production.- Resource names stay application-specific. Nothing in the AppHost is coupled to a particular Azure resource group or subscription.
Multiple databases
If a single Azure SQL server hosts several databases, define each one as a separate connection string and reference them independently from the AppHost. The pattern scales linearly:
Each connection string is its own resource. The API project receives all three names under ConnectionStrings: in its configuration, exactly as it would in production.
On-premises and external SQL
The same pattern handles on-premises SQL Server, third-party hosted databases, and any other resource that already exists outside Aspire's control. The connection string is the contract:
Resist the temptation to pass connection strings via .WithEnvironment("ConnectionStrings:...") when a connection-string resource is available. The AddConnectionString + WithReference path is more discoverable, type-safe in the AppHost, and integrates cleanly with Aspire's Dashboard.
Best practices
Security. Use Azure Key Vault for production secrets - the User Secrets pattern is for development only. Apply least-privilege access on the database and storage account. Never commit any secret to source control, even briefly.
Cost management. Hybrid setups touch real Azure resources, which means real Azure billing. Use dev/test pricing tiers where they exist. Monitor usage with Azure Cost Management and set budget alerts so a runaway dev environment cannot quietly accumulate charges.
Performance. Expect some network latency from local to Azure - typically tens of milliseconds, sometimes more depending on region. Use connection pooling. Configure retry policies for transient failures, especially with Azure SQL.
Data management. Keep dev/test data separate from production. Apply migrations and seed scripts to the dev database the same way you would in CI. Back up data that took real work to construct.
Troubleshooting checklist
When something does not connect, work through the symptoms in this order - the cause is almost always one of these:
- Connection timeouts. The Azure resource's firewall is blocking your IP. Add your IP to the SQL server's firewall rules, or enable "Allow Azure services and resources to access this server" if you are running through an Azure tunnel.
- Authentication errors. Verify the identity your local environment is using -
az loginfor Active Directory Default authentication, or the storage account key for AccountKey-based connection strings. Check that the identity has the right role on the resource. - Slow performance. Check latency to the region. Confirm retry policies are configured. Look at the Aspire Dashboard's traces to see which call is actually slow.
- "Cannot find connection string AppDb". The connection string is missing from User Secrets and from
appsettings.Development.json. Aspire matches on the name passed toAddConnectionString()- make sure the JSON key matches exactly.
For deeper debugging, enable diagnostic logging on the Azure resources themselves (Azure SQL's "Diagnostics settings", Storage's logging blade) and use Azure Storage Explorer or the Azure Portal's Query Editor to confirm the resource is reachable from outside Aspire entirely. If a tool other than Aspire can talk to the resource, the problem is in the AppHost; if neither can, the problem is in Azure.
References
- What is Aspire?stacknova · engineering · aspire
- What Aspire does and does not dostacknova · engineering · aspire
- With and Without Aspirestacknova · engineering · aspire
- Aspire official siteaspire.dev
- Microsoft Learn · External parameters and connection stringslearn.microsoft.com/dotnet/aspire
- User Secrets in ASP.NET Corelearn.microsoft.com/aspnet/core