The goal
By the end you'll have an Azure SQL resource modeled in your AppHost but running as a local SQL Server container, a database nested under it, and a service connected through an injected ConnectionStrings__appdb - with no Azure subscription and nothing to pay for.
The idea that makes this distinct from a plain local database: you declare an Azure SQL resource in the app model, but run it as a local container during development. One model, two runtimes - locally it is a SQL Server container, and on publish the same model emits Bicep for real Azure SQL. The local path costs nothing and needs no Azure account. The machinery behind that split is run mode vs publish mode.
The four ways to attach it
Aspire gives you four ways to attach Azure SQL. They split into two groups: Local options that need no Azure account, and Azure options for cloud-connected work. This guide walks the two Local options in full - run it as a container (the default) and point at your own instance.
Run as a local container
RecommendedAddAzureSqlServer("sql").RunAsContainer()Connection string (your DB)
AddConnectionString("appdb")Provision a new Azure SQL
AddAzureSqlServer("sql")Reference an existing Azure SQL
AddAzureSqlServer("sql").AsExisting(name, rg)Three of these share one model (AddAzureSqlServer) - the container and both Azure options; RunAsContainer just redirects local runs to a container. The connection-string option is the odd one out: Aspire owns only the wiring, not the database. That split - managed vs referenced resources - is the same idea underneath all four.
Run as a local container (option 1). It needs no Azure account, costs nothing, and your code is identical to production - the same model publishes to real Azure SQL. Reach for option 2 only when you already run SQL Server locally and want to keep managing it in SSMS yourself.
Prerequisites
aspire --versionRunAsContainer keeps everything on your machineRun as a local container
The default path - Aspire models the database as Azure SQL but runs it locally as a SQL Server container, so you need no Azure account and your code is identical to production.
Add the Azure SQL hosting integration to the AppHost
ExpectedAspire.Hosting.Azure.Sql is added to AppHost.csproj; AddAzureSqlServer is now in scope.
Model the Azure SQL server, run it as a container, add a database
RunAsContainer is the key call: AddAzureSqlServer turns on Azure provisioning by default, and RunAsContainer overrides that for local runs only. A persistent lifetime keeps your data across restarts.
ExpectedThe solution builds. Locally Aspire runs mcr.microsoft.com/mssql/server instead of provisioning anything in Azure.
Reference the database from your service
Wire the database into the consuming project. WaitFor holds the service until the container passes its health check - the SQL Server image takes a moment to come up.
ExpectedAt run time the apiservice project receives a ConnectionStrings__appdb environment variable.
Add the SQL client integration to your service
This goes in the consuming project, not the AppHost. It brings the typed SqlConnection wiring backed by Microsoft.Data.SqlClient.
ExpectedAspire.Microsoft.Data.SqlClient is added to ApiService.csproj; AddSqlServerClient is in scope.
Register the client, then run it
The connectionName must match the AddDatabase name - "appdb", not the server name "sql". Then start the whole topology with the Aspire CLI.
ExpectedRun aspire run. The dashboard opens with sql running as a container, appdb nested under it, and apiservice connected - no Azure subscription prompt.
Use your local instance instead
You already run SQL Server locally and manage it in SSMS, and you want no container and no Azure account. Aspire owns only the wiring - the connection string - not the database itself.
This is option 2 (Local) from the table above. Instead of AddAzureSqlServer, you model a connection string and Aspire passes it straight through to your service. The client steps (4 and 5 below) are identical to the container path - only the AppHost and configuration change.
Model a connection string in the AppHost
No hosting package, no container. AddConnectionString tells Aspire to read a value from configuration and inject it - nothing is provisioned or started.
ExpectedNo SQL container is started. apiservice will receive ConnectionStrings__appdb from configuration.
Store your local connection string in User Secrets
Use the same server name you use in SSMS - localhost, .\SQLEXPRESS, or (localdb)\MSSQLLocalDB. TrustServerCertificate=True keeps local dev simple.
ExpectedThe string is stored outside source control; the AppHost reads it for the appdb connection at run time.
Add the SQL client integration to your service
Same package and same call as the container path - the consuming project doesn't know or care which backs the connection.
ExpectedAspire.Microsoft.Data.SqlClient is added to ApiService.csproj; AddSqlServerClient is in scope.
Register the client - the name still matches
The connectionName matches the AddConnectionString name, exactly as it matched AddDatabase on the container path.
ExpectedSqlConnection resolves from DI, pointed at your SSMS-managed instance - no container involved.
Run it
Start the topology with the Aspire CLI. The database is yours; Aspire just wired the string into the service.
ExpectedThe dashboard lists appdb as a connection-string resource (not a container), and apiservice connects straight to your local database.
Verify
For the container path you're done when every one of these holds:
- The dashboard lists the sql resource running as a container (the SQL Server image), state Running.
- The appdb database is nested under sql, state Ready.
-
The apiservice environment shows
ConnectionStrings__appdbinjected by Aspire. - No subscription or location prompt appears, and nothing is billed - the local path never touches Azure.
- A query from apiservice succeeds against the local container through the injected connection.
On the local-instance path (option 2) the done-state is different: appdb appears in the dashboard as a connection string rather than a container, and apiservice connects to the database you manage in SSMS.
Troubleshooting
SymptomThe dashboard tries to provision Azure, or shows missing configuration: subscription, location.
FixYou didn't chain .RunAsContainer(). AddAzureSqlServer implicitly enables Azure provisioning; RunAsContainer is what redirects the local run to a SQL Server container so no subscription is needed.
SymptomThe sql container is slow or unhealthy on first run.
FixThe SQL Server image is large - the first pull takes a while and the container needs ~2 GB of memory and a few seconds to report healthy. Keep .WaitFor(db) so apiservice waits, and give Docker or Podman enough memory.
SymptomAddSqlServerClient can't find the connection string.
FixThe connectionName must match the AddDatabase name (or the AddConnectionString name) exactly - "appdb", not the server resource name "sql".
SymptomOn the local-instance path, the app can't connect: login failed or a certificate error.
FixUse the server name your SSMS uses - localhost, .\SQLEXPRESS, or (localdb)\MSSQLLocalDB - and add TrustServerCertificate=True for local dev. Make sure TCP/IP is enabled and the auth mode (Windows vs SQL) matches your connection string.
References
- Set up Azure SQL Database in the AppHost - RunAsContaineraspire.dev/integrations/cloud/azure/azure-sql-database
- Get started with the Azure SQL Database integrationsaspire.dev/integrations/cloud/azure/azure-sql-database
- Connect to Azure SQL Database - AddSqlServerClientaspire.dev/integrations/cloud/azure/azure-sql-database
- Aspire.Hosting.Azure.Sql on NuGetnuget.org/packages/Aspire.Hosting.Azure.Sql
- Inside Aspire's AppHost - run mode vs publish modestacknova · engineering · apphost