Skip to content

[New article]: WithHealthCheck must be on the emulator container inside RunAsEmulator, not the outer Azure resource #6312

@clongfei

Description

@clongfei

Proposed topic or title

Document that WithHealthCheck must be on the emulator container inside RunAsEmulator, not the outer Azure resource

Location in table of contents.

https://aspire.dev/fundamentals/health-checks/

Reason for the article

Summary

The health checks documentation shows how to use WithHealthCheck to associate custom health checks with resources for WaitFor gating. However, it does not cover how this works with Azure resources running as emulators via RunAsEmulator.

Through trial and error, I discovered that WithHealthCheck must be called on the emulator container resource inside the RunAsEmulator callback, not on the outer Azure resource builder. This is a non-obvious but critical distinction for anyone trying to add custom health checks to emulated Azure resources.

The problem

When using RunAsEmulator() for Azure resources (Cosmos DB, Event Hubs, etc.), the emulator container may report as healthy before it's actually ready to accept connections. This is a known issue (see dotnet/aspire#9880, dotnet/aspire#5097). To mitigate this, users need to register custom deep health checks and associate them with the resource so that WaitFor blocks dependent services until the emulator is truly ready.

What does NOT work

Associating the health check with the outer Azure resource:

var cosmos = builder.AddAzureCosmosDB("CosmosDbConnection");

builder.Services.AddHealthChecks()
    .AddAsyncCheck("cosmos-connectivity", async ct =>
    {
        // ... verify actual database connectivity ...
    });

cosmos = cosmos.RunAsEmulator(container =>
{
    container.WithLifetime(ContainerLifetime.Session);
});

// ❌ This does NOT gate WaitFor — the Function/API starts before the check passes
cosmos.WithHealthCheck("cosmos-connectivity");

With this pattern, WaitFor(cosmos) does not wait for the custom health check. Dependent services start before the emulator is ready, causing startup failures.

What DOES work

Associating the health check with the emulator container inside the RunAsEmulator callback:

var cosmos = builder.AddAzureCosmosDB("CosmosDbConnection");

builder.Services.AddHealthChecks()
    .AddAsyncCheck("cosmos-connectivity", async ct =>
    {
        // ... verify actual database connectivity ...
    });

cosmos = cosmos.RunAsEmulator(container =>
{
    container.WithLifetime(ContainerLifetime.Session);

    // ✅ Associate with the container resource, not the outer Azure resource
    container.WithHealthCheck("cosmos-connectivity");
});

With this pattern, WaitFor(cosmos) correctly waits for the custom health check to return Healthy before starting dependent services.

Why this matters

The RunAsEmulator pattern wraps the Azure resource with an underlying container resource. WaitFor checks health on the container, not the outer Azure resource wrapper. This is not documented anywhere, and the current health checks documentation only shows examples with simple containers and projects — not the RunAsEmulator scenario.

Article abstract

The health checks page should include:

A section or example showing how to add custom health checks to emulated Azure resources using WithHealthCheck inside the RunAsEmulator callback.

A note explaining that when an Azure resource runs as an emulator, health checks must be associated with the emulator container (via the callback parameter), not the outer Azure resource builder.

A practical example like:

// Register the health check in the service collection
builder.Services.AddHealthChecks()
    .AddAsyncCheck("cosmos-deep-check", async ct =>
    {
        var connectionString = await ((IResourceWithConnectionString)cosmos.Resource)
            .GetConnectionStringAsync(ct);
        if (string.IsNullOrEmpty(connectionString))
            return HealthCheckResult.Unhealthy("Connection string not available");

        using var client = new CosmosClient(connectionString, new CosmosClientOptions
        {
            ConnectionMode = ConnectionMode.Gateway,
            LimitToEndpoint = true,
        });
        await client.CreateDatabaseIfNotExistsAsync("mydb", cancellationToken: ct);
        return HealthCheckResult.Healthy();
    });

// Associate with the emulator container inside RunAsEmulator
cosmos = cosmos.RunAsEmulator(container =>
{
    container.WithHealthCheck("cosmos-deep-check");
});

// WaitFor now correctly waits for the deep health check
builder.AddProject<Projects.MyApi>("myapi")
    .WaitFor(cosmos);

Relevant searches

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Pri1High priority, do before Pri2 and Pri3area-docsdoc-ideaIndicates issues that are suggestions for new topics [org][type][category]

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions