The goal

End state

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.

Local - no Azure account
1

Run as a local container

Recommended
AddAzureSqlServer("sql").RunAsContainer()
LocalSQL Server container, no Azure account Publishreal Azure SQL via Bicep
2

Connection string (your DB)

AddConnectionString("appdb")
Localanything, incl. your local SSMS instance - no container Publishsame string from config
Azure - cloud-connected
3

Provision a new Azure SQL

AddAzureSqlServer("sql")
Localprovisions in Azure (needs a subscription) Publishreal Azure SQL via Bicep
4

Reference an existing Azure SQL

AddAzureSqlServer("sql").AsExisting(name, rg)
Local + publishyour already-provisioned server, strongly typed

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.

Recommended

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

.NET SDK
10.0.100 or later
Aspire CLI
13.3 or later - check with aspire --version
Container runtime
Docker Desktop or Podman, running - Aspire pulls the SQL Server image into it
Free memory
~2 GB for the SQL Server container
Azure account
Not needed for the local path - RunAsContainer keeps everything on your machine
Starting point
An Aspire solution with an AppHost and a referenced project (the service that will query)

Run 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.

1

Add the Azure SQL hosting integration to the AppHost

Shellsolution root
dotnet add AppHost package Aspire.Hosting.Azure.Sql

ExpectedAspire.Hosting.Azure.Sql is added to AppHost.csproj; AddAzureSqlServer is now in scope.

2

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.

C#AppHost.cs
var builder = DistributedApplication.CreateBuilder(args); // modeled as Azure SQL, but run locally as a SQL Server container var sql = builder.AddAzureSqlServer("sql") .RunAsContainer(c => c.WithLifetime(ContainerLifetime.Persistent)); var db = sql.AddDatabase("appdb");

ExpectedThe solution builds. Locally Aspire runs mcr.microsoft.com/mssql/server instead of provisioning anything in Azure.

3

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.

C#AppHost.cs
builder.AddProject<Projects.AspireAzureSql_ApiService>("apiservice") .WithReference(db) // inject ConnectionStrings__appdb .WaitFor(db); // wait until the db is ready builder.Build().Run();

ExpectedAt run time the apiservice project receives a ConnectionStrings__appdb environment variable.

4

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.

Shellsolution root
dotnet add ApiService package Aspire.Microsoft.Data.SqlClient

ExpectedAspire.Microsoft.Data.SqlClient is added to ApiService.csproj; AddSqlServerClient is in scope.

5

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.

C#ApiService/Program.cs
var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); builder.AddSqlServerClient(connectionName: "appdb"); // matches AddDatabase("appdb")

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

When to use this

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.

1

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.

C#AppHost.cs
var builder = DistributedApplication.CreateBuilder(args); // Aspire owns only the wiring - your SSMS instance owns the data var appdb = builder.AddConnectionString("appdb"); builder.AddProject<Projects.AspireAzureSql_ApiService>("apiservice") .WithReference(appdb); builder.Build().Run();

ExpectedNo SQL container is started. apiservice will receive ConnectionStrings__appdb from configuration.

2

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.

Shellsolution root
dotnet user-secrets set "ConnectionStrings:appdb" "Server=localhost;Database=AppDb;Trusted_Connection=True;TrustServerCertificate=True;" --project AppHost

ExpectedThe string is stored outside source control; the AppHost reads it for the appdb connection at run time.

3

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.

Shellsolution root
dotnet add ApiService package Aspire.Microsoft.Data.SqlClient

ExpectedAspire.Microsoft.Data.SqlClient is added to ApiService.csproj; AddSqlServerClient is in scope.

4

Register the client - the name still matches

The connectionName matches the AddConnectionString name, exactly as it matched AddDatabase on the container path.

C#ApiService/Program.cs
var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); builder.AddSqlServerClient(connectionName: "appdb"); // matches AddConnectionString("appdb")

ExpectedSqlConnection resolves from DI, pointed at your SSMS-managed instance - no container involved.

5

Run it

Start the topology with the Aspire CLI. The database is yours; Aspire just wired the string into the service.

Shellsolution root
aspire run

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__appdb injected 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.