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.
Prerequisites
Section titled “Prerequisites”- .NET 10 SDK
- Git
- Azurite for local clustering
- An Azure subscription and Azure CLI for deployment
Get the application
Section titled “Get the application”From an empty working directory:
git clone https://github.com/dotnet/orleans.gitcd orleansdotnet build .\samples\Deployment\AzureContainerApps\HelloOrleans.sln -c ReleaseExplore the projects before running them:
| Project | Responsibility |
|---|---|
Abstractions | Grain interfaces and serialized contracts shared by callers and silos. |
Grains | Grain implementations and placement policy. |
Silo | A dedicated Orleans server process. |
Clients.MinimalApi | An HTTP API which calls grains through an Orleans client. |
Clients.WorkerService | A background client which sends simulated sensor updates. |
Dashboard | A separately deployed dashboard silo. |
Infrastructure | Shared identity, clustering, endpoint, and telemetry configuration. |
This separation lets clients and silos scale and deploy independently. It also keeps contracts independent from implementations.
Run the system locally
Section titled “Run the system locally”Start Azurite, then open four terminals at the repository root:
dotnet run --project .\samples\Deployment\AzureContainerApps\Silodotnet run --project .\samples\Deployment\AzureContainerApps\Dashboarddotnet run --project .\samples\Deployment\AzureContainerApps\Clients.MinimalApidotnet run --project .\samples\Deployment\AzureContainerApps\Clients.WorkerServiceEach launch profile selects the Development environment and connects to Azurite. Verify the system before deploying:
- Open the dashboard URL printed by the
Dashboardprocess and confirm that its silo and the dedicated silo are active. - Call
GET /hello/0on the Minimal API and confirm that it returns a greeting. - Call
GET /providersand confirm that grain key0appears. - 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.
Understand the production configuration
Section titled “Understand the production configuration”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.
Configure the host in code
Section titled “Configure the host in code”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.
Choose a deployment model
Section titled “Choose a deployment model”Use the platform guide whose network and lifecycle guarantees match the target environment:
| Target | Recommended model |
|---|---|
| Kubernetes | Advertise each pod IP, allow direct pod-to-pod TCP, and use a production clustering provider. |
| Managed container platform | Give each silo a documented per-instance address or a unique private address and port pair. |
| Virtual machines or bare metal | Advertise stable private addresses and supervise the .NET host as a long-running service. |
| Azure App Service | Use 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.
Deploy
Section titled “Deploy”- Fork the Orleans repository and enable GitHub Actions.
- Configure a GitHub OIDC identity restricted to your fork and a protected
azure-container-appsenvironment. - Run the sample’s one-time privileged bootstrap to create the registry, membership storage, runtime identity, and least-privilege role assignments.
- Copy
samples/Deployment/AzureContainerApps/deployment/deploy.ymlto.github/workflows/deploy-orleans-container-apps.yml. - Configure the dashboard host and ingress for the operator controls described above.
- 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.
Verify the deployment
Section titled “Verify the deployment”Connect through the operator-only administrative path, then:
- Confirm that every expected silo is active in the dashboard.
- Exercise
GET /hello/0,GET /hello/255, andGET /providers. - Verify that invalid grain keys return HTTP 400.
- Confirm that startup, readiness, and liveness probes are healthy.
- 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.
