Skip to content

Deploy an Orleans application to Azure Container Apps

This walkthrough takes you from an empty directory to a deployed, observable Orleans cluster using the Azure Container Apps sample. The application, infrastructure, and deployment workflow are versioned and validated together.

The finished system has dedicated silos, an HTTP API and worker client, Azure Table Storage clustering, managed identity, health probes, Application Insights, and an Orleans Dashboard. This sample demonstrates clustering and deployment; add a grain-storage provider to persist application state.

From an empty working directory:

Terminal window
git clone https://github.com/dotnet/orleans.git
cd orleans
dotnet build .\samples\Deployment\AzureContainerApps\HelloOrleans.sln -c Release

Explore the projects before running them:

ProjectResponsibility
AbstractionsGrain interfaces and serialized contracts shared by callers and silos.
GrainsGrain implementations and placement policy.
SiloA dedicated Orleans server process.
Clients.MinimalApiAn HTTP API which calls grains through an Orleans client.
Clients.WorkerServiceA background client which sends simulated sensor updates.
DashboardA separately deployed dashboard silo.
InfrastructureShared identity, clustering, endpoint, and telemetry configuration.

This separation lets clients and silos scale and deploy independently. It also keeps contracts independent from implementations.

Start Azurite, then open four terminals at the repository root:

Terminal window
dotnet run --project .\samples\Deployment\AzureContainerApps\Silo
dotnet run --project .\samples\Deployment\AzureContainerApps\Dashboard
dotnet run --project .\samples\Deployment\AzureContainerApps\Clients.MinimalApi
dotnet run --project .\samples\Deployment\AzureContainerApps\Clients.WorkerService

Each launch profile selects the Development environment and connects to Azurite. Verify the system before deploying:

  1. Open the dashboard URL printed by the Dashboard process and confirm that its silo and the dedicated silo are active.
  2. Call GET /hello/0 on the Minimal API and confirm that it returns a greeting.
  3. Call GET /providers and confirm that grain key 0 appears.
  4. Stop the worker and confirm that API calls continue; the worker and Minimal API are separate Orleans clients, so restarting the worker leaves the API client connected to the silo cluster.

The sample dashboard uses open access for local development. Keep that endpoint on a trusted local host. For a deployed environment, secure the dashboard with HTTPS, operator authentication and authorization, and a private administrative path.

Development uses a storage emulator. Deployed processes use DefaultAzureCredential with a user-assigned managed identity and an Azure Table service URI. Token-based data-plane access supplies storage authorization.

Every deployed silo has a unique advertised silo and gateway endpoint. Azure Container Apps assigns the documented endpoint at the app boundary, so the sample deploys each silo as a separate one-replica Container App. Add capacity by adding silo apps with unused ports.

Review the sample’s deployment README and Azure/bootstrap.bicep before assigning roles. The privileged bootstrap and routine deployment are deliberately separate.

Orleans production configuration is composed on the .NET Generic Host. The sample silo host reads deployment values through IConfiguration, calls UseOrleans, and configures cluster identity, endpoints, and Azure Table Storage clustering on the resulting ISiloBuilder.

The same pattern can register durable grain storage in an application which persists grain state:

using Azure.Data.Tables;
using Azure.Identity;
using Orleans.Configuration;
var tableEndpoint = new Uri(
builder.Configuration["AZURE_TABLE_STORAGE_ENDPOINT"]
?? throw new InvalidOperationException("AZURE_TABLE_STORAGE_ENDPOINT isn't configured."));
var tableServiceClient = new TableServiceClient(
tableEndpoint,
new DefaultAzureCredential());
builder.Host.UseOrleans(siloBuilder =>
{
siloBuilder
.Configure<ClusterOptions>(options =>
{
options.ServiceId = "orders";
options.ClusterId = builder.Configuration["ORLEANS_CLUSTER_ID"]
?? throw new InvalidOperationException("ORLEANS_CLUSTER_ID isn't configured.");
})
.UseAzureStorageClustering(
options => options.TableServiceClient = tableServiceClient)
.AddAzureTableGrainStorage(
name: "default",
options => options.TableServiceClient = tableServiceClient);
});

ServiceId remains stable for the application. ClusterId identifies the deployment environment or blue-green cluster. Every silo and external client uses the same values and the same clustering backend. The host reads provider endpoints and cluster identity from deployment configuration and fails startup when required values are absent.

Listening endpoints describe where the process accepts connections. Advertised endpoints identify the unique address and ports which other silos and clients use to reach that process:

using System.Net;
using Orleans.Configuration;
var advertisedAddress = IPAddress.Parse(
builder.Configuration["ORLEANS_ADVERTISED_IP"]
?? throw new InvalidOperationException("ORLEANS_ADVERTISED_IP isn't configured."));
var advertisedSiloPort = int.Parse(
builder.Configuration["ORLEANS_ADVERTISED_SILO_PORT"]
?? throw new InvalidOperationException("ORLEANS_ADVERTISED_SILO_PORT isn't configured."));
var advertisedGatewayPort = int.Parse(
builder.Configuration["ORLEANS_ADVERTISED_GATEWAY_PORT"]
?? throw new InvalidOperationException("ORLEANS_ADVERTISED_GATEWAY_PORT isn't configured."));
builder.Host.UseOrleans(siloBuilder =>
{
siloBuilder.Configure<EndpointOptions>(options =>
{
options.AdvertisedIPAddress = advertisedAddress;
options.SiloPort = advertisedSiloPort;
options.GatewayPort = advertisedGatewayPort;
options.SiloListeningEndpoint = new IPEndPoint(IPAddress.Any, 11_111);
options.GatewayListeningEndpoint = new IPEndPoint(IPAddress.Any, 30_000);
});
});

The deployment platform supplies these values for each silo. The sample’s endpoint configuration applies the same model to the private address and unique port pair allocated to each one-replica Container App.

Use the platform guide whose network and lifecycle guarantees match the target environment:

TargetRecommended model
KubernetesAdvertise each pod IP, allow direct pod-to-pod TCP, and use a production clustering provider.
Managed container platformGive each silo a documented per-instance address or a unique private address and port pair.
Virtual machines or bare metalAdvertise stable private addresses and supervise the .NET host as a long-running service.
Azure App ServiceUse the multi-instance sample and its private per-instance port mapping.

See Platform requirements before adapting the sample to another host. The invariant is that every membership entry names one silo endpoint which all other silos can reach directly.

  1. Fork the Orleans repository and enable GitHub Actions.
  2. Configure a GitHub OIDC identity restricted to your fork and a protected azure-container-apps environment.
  3. Run the sample’s one-time privileged bootstrap to create the registry, membership storage, runtime identity, and least-privilege role assignments.
  4. Copy samples/Deployment/AzureContainerApps/deployment/deploy.yml to .github/workflows/deploy-orleans-container-apps.yml.
  5. Configure the dashboard host and ingress for the operator controls described above.
  6. Add the environment variables listed in the sample README, then run the workflow.

The workflow builds images, pushes immutable Git-SHA tags, deploys by image digest, and authenticates through GitHub OIDC and workload identity.

Connect through the operator-only administrative path, then:

  1. Confirm that every expected silo is active in the dashboard.
  2. Exercise GET /hello/0, GET /hello/255, and GET /providers.
  3. Verify that invalid grain keys return HTTP 400.
  4. Confirm that startup, readiness, and liveness probes are healthy.
  5. Inspect traces and logs in Application Insights and confirm that requests cross the API-to-grain boundary.

Before adapting this system for production, work through the production-readiness checklist, configure durable grain storage, and plan graceful shutdown and upgrades.