Dapr for Beginners: Running Dapr Pub/Sub Apps on Azure Container Apps
-
Ahmed Muhi - 28 Aug, 2025
Introduction: From Local Dapr to Azure Container Apps
In the previous article, we built a small event-driven application with Dapr.
The application had two services:
Order Service
→ publishes an OrderPlaced event
Inventory Service
→ subscribes to OrderPlaced
→ updates inventory state
Everything ran locally.
Dapr used the local components created by dapr init. The pub/sub component used Redis behind the scenes, and the state store also used local Dapr-managed infrastructure.
That was the right place to learn the model.
But local development is not the final destination.
In a real cloud environment, we usually do not want the application to depend on local Redis containers. We want managed cloud services behind the Dapr components.
That is where Azure Container Apps fits into the series.
Azure Container Apps can run our containerized services and enable Dapr for each app. Instead of manually running a Dapr sidecar beside each local process, we deploy each service as a Container App and enable Dapr as part of the platform configuration.
The application idea stays the same:
Order Service
→ publishes OrderPlaced
Inventory Service
→ subscribes and reacts
But the infrastructure behind Dapr changes:
Local version:
Dapr pub/sub
→ Redis
Dapr state
→ Redis
Azure version:
Dapr pub/sub
→ Azure Service Bus
Dapr state
→ Azure Blob Storage
That is the main point of this article.
The application code still talks to Dapr.
Dapr components decide what infrastructure sits behind the APIs.
In this walkthrough, we will take the same Order and Inventory services and run them on Azure Container Apps:
Public Order Container App
→ receives an order request
→ publishes OrderPlaced through Dapr pub/sub
Internal Inventory Container App
→ subscribes to OrderPlaced
→ updates inventory state through Dapr state management
Azure Service Bus
→ backs the Dapr pub/sub component
Azure Blob Storage
→ backs the Dapr state store component
The goal is not to teach every part of Azure Container Apps or every feature of Azure Service Bus.
The goal is simpler:
Take the Dapr app we already understand,
move it into Azure,
and change the Dapr components from local backends
to Azure-backed services.
By the end, you should understand the most important Dapr cloud idea:
Your application code keeps using Dapr APIs.
The backend infrastructure can change behind those APIs.
What We Are Building
We will build the cloud version of the same Dapr Pub/Sub application from the previous article.
The local version looked like this:
Order Service
→ publishes OrderPlaced through Dapr pub/sub
Inventory Service
→ subscribes to OrderPlaced
→ updates inventory through Dapr state management
The Azure version keeps that same application flow, but changes where the services run and what sits behind the Dapr components.
The cloud architecture will look like this:
Browser or curl
→ public Order Container App
→ Dapr pub/sub API
→ Azure Service Bus-backed pub/sub component
→ internal Inventory Container App
→ Dapr state API
→ Azure Blob Storage-backed state store
The application will have two Container Apps.
The Order Container App will be public.
It will receive an order request and publish an OrderPlaced event through Dapr:
Order Container App
→ app ID: order-service
→ publishes to topic: OrderPlaced
→ uses Dapr component: pubsub
The Inventory Container App will be internal.
It will subscribe to the OrderPlaced topic and update inventory state when an event arrives:
Inventory Container App
→ app ID: inventory-service
→ subscribes to topic: OrderPlaced
→ uses Dapr component: pubsub
→ stores state through component: statestore
The backing Azure services will be:
Azure Container Registry
→ stores the Order and Inventory container images
Azure Container Apps environment
→ hosts both Container Apps and their Dapr sidecars
Azure Service Bus
→ backs the Dapr pub/sub component
Azure Blob Storage
→ backs the Dapr state store component
The important detail is that we will keep the Dapr component names familiar:
pubsub
→ the Dapr pub/sub component name
statestore
→ the Dapr state store component name
That means the application code can keep using the same Dapr building block names it used locally.
The main change is the component configuration.
Locally, Dapr used Redis behind the scenes.
In Azure, Dapr will use managed Azure services behind the scenes.
So the article is really about this shift:
Same application code
Same Dapr APIs
Same app IDs
Same component names
Different hosting platform
Different backend infrastructure
That is the cloud payoff of Dapr’s building-block model.
What Changes When Dapr Moves to Azure
Before we start creating Azure resources, it is worth naming what changes and what stays the same.
The most important thing that stays the same is the application model.
The Order Service still publishes an OrderPlaced event through Dapr.
The Inventory Service still subscribes to OrderPlaced and updates inventory state through Dapr.
The application code still thinks in Dapr terms:
Publish to pubsub.
Subscribe to OrderPlaced.
Save state in statestore.
What changes is the environment around the application.
Locally, each service ran as a normal process with a Dapr sidecar started by the Dapr CLI:
dapr run
→ starts your app
→ starts a local Dapr sidecar beside it
In Azure Container Apps, the platform runs the containerized service and enables Dapr for that Container App:
Container App
→ runs your service container
→ has Dapr enabled
→ gets a Dapr sidecar managed by the platform
The backing components also change.
Locally, dapr init gave us default local components. For this app, Redis was enough to support pub/sub and state.
In Azure, we will configure Dapr components that point to managed Azure services:
pubsub
→ Azure Service Bus
statestore
→ Azure Blob Storage
So the mapping is:
Local app process
→ Azure Container App
Local Dapr sidecar from dapr run
→ Dapr enabled on the Container App
Local Redis pub/sub
→ Azure Service Bus-backed Dapr pub/sub component
Local Redis state store
→ Azure Blob Storage-backed Dapr state component
Localhost test
→ public Order Container App URL
That is the key idea for the rest of the walkthrough.
We are not changing the application pattern.
We are changing the hosting platform and the component configuration behind Dapr.
Prepare the Azure Resources
The Azure version needs a few resources before we can deploy the two services.
We will create:
Resource group
Azure Container Registry
Container Apps environment
Azure Service Bus namespace
Azure Service Bus topic
Storage account
Blob container
Azure Container Registry will store the Order and Inventory images.
The Container Apps environment will host the two Container Apps and their Dapr sidecars.
Azure Service Bus will back the Dapr pubsub component.
Azure Blob Storage will back the Dapr statestore component. Dapr’s Azure Service Bus Topics component uses pubsub.azure.servicebus.topics, and its Azure Blob Storage state component uses state.azure.blobstorage.
Start by setting variables:
$RESOURCE_GROUP = "rg-dapr-aca-pubsub-demo"
$LOCATION = "australiaeast"
$ACR_NAME = "<globally-unique-acr-name>"
$CONTAINERAPPS_ENVIRONMENT = "cae-dapr-pubsub-demo"
$SERVICE_BUS_NAMESPACE = "<globally-unique-servicebus-name>"
$SERVICE_BUS_TOPIC = "orderplaced"
$STORAGE_ACCOUNT_NAME = "<globally-unique-storage-account-name>"
$BLOB_CONTAINER_NAME = "dapr-state"
The Azure Container Registry name, Service Bus namespace, and storage account name must be globally unique. Keep them lowercase and simple.
For example:
$ACR_NAME = "dapracademo12345"
$SERVICE_BUS_NAMESPACE = "daprpubsubdemo12345"
$STORAGE_ACCOUNT_NAME = "daprstate12345"
Create the resource group:
az group create `
--name $RESOURCE_GROUP `
--location $LOCATION
Create the Azure Container Registry:
az acr create `
--resource-group $RESOURCE_GROUP `
--name $ACR_NAME `
--sku Basic
Create the Container Apps environment:
az containerapp env create `
--name $CONTAINERAPPS_ENVIRONMENT `
--resource-group $RESOURCE_GROUP `
--location $LOCATION
The Container Apps environment is the managed boundary where both services will run.
It is also where we will add the Dapr components later. In Azure Container Apps, Dapr components are configured at the environment level and can be shared by Dapr-enabled apps in that environment.
Create the Azure Service Bus namespace:
az servicebus namespace create `
--resource-group $RESOURCE_GROUP `
--name $SERVICE_BUS_NAMESPACE `
--location $LOCATION `
--sku Standard
Create the Service Bus topic:
az servicebus topic create `
--resource-group $RESOURCE_GROUP `
--namespace-name $SERVICE_BUS_NAMESPACE `
--name $SERVICE_BUS_TOPIC
We use the Standard SKU because this walkthrough uses a Service Bus topic. The Dapr component will connect to this namespace and topic through the pubsub component.
Now create the storage account:
az storage account create `
--resource-group $RESOURCE_GROUP `
--name $STORAGE_ACCOUNT_NAME `
--location $LOCATION `
--sku Standard_LRS `
--min-tls-version TLS1_2
Get the storage account key:
$STORAGE_ACCOUNT_KEY = az storage account keys list `
--resource-group $RESOURCE_GROUP `
--account-name $STORAGE_ACCOUNT_NAME `
--query "[0].value" `
--output tsv
Create the blob container that Dapr will use for state:
az storage container create `
--name $BLOB_CONTAINER_NAME `
--account-name $STORAGE_ACCOUNT_NAME `
--account-key $STORAGE_ACCOUNT_KEY
At this point, the Azure foundation is ready:
Azure Container Registry
→ stores the service images
Container Apps environment
→ hosts the Order and Inventory apps
Azure Service Bus topic
→ backs Dapr pub/sub
Azure Blob Storage container
→ backs Dapr state
Next, we will build the Order and Inventory service images and push them to Azure Container Registry.
Build and Push the Service Images
Now that the Azure resources are ready, we need to package the two services as container images and push them to Azure Container Registry.
We will build and push two images:
Order Service
→ order-service image
→ Azure Container Registry
Inventory Service
→ inventory-service image
→ Azure Container Registry
First, get the Azure Container Registry login server:
$ACR_LOGIN_SERVER = az acr show `
--name $ACR_NAME `
--query loginServer `
--output tsv
Check the value:
$ACR_LOGIN_SERVER
It should look something like this:
dapracademo12345.azurecr.io
Now set the image names and tag:
$ORDER_IMAGE_NAME = "order-service"
$INVENTORY_IMAGE_NAME = "inventory-service"
$IMAGE_TAG = "1.0"
Sign in to Azure Container Registry:
az acr login --name $ACR_NAME
Build the Order Service image:
docker build `
-t "$ACR_LOGIN_SERVER/$ORDER_IMAGE_NAME`:$IMAGE_TAG" `
./order-service
Build the Inventory Service image:
docker build `
-t "$ACR_LOGIN_SERVER/$INVENTORY_IMAGE_NAME`:$IMAGE_TAG" `
./inventory-service
These commands assume you are running them from the project root folder that contains both service folders:
dapr-pubsub-demo/
├── order-service/
│ ├── order_service.js
│ ├── package.json
│ └── Dockerfile
└── inventory-service/
├── inventory_service.py
├── requirements.txt
└── Dockerfile
The image names include the ACR login server:
<acr-name>.azurecr.io/order-service:1.0
<acr-name>.azurecr.io/inventory-service:1.0
That tells Docker where the images should be pushed.
Push the Order Service image:
docker push "$ACR_LOGIN_SERVER/$ORDER_IMAGE_NAME`:$IMAGE_TAG"
Push the Inventory Service image:
docker push "$ACR_LOGIN_SERVER/$INVENTORY_IMAGE_NAME`:$IMAGE_TAG"
Confirm that both repositories exist in Azure Container Registry:
az acr repository list `
--name $ACR_NAME `
--output table
You should see:
inventory-service
order-service
At this point, Azure Container Apps can pull both images from Azure Container Registry.
The application code is now packaged and ready for deployment:
Local service code
→ Docker image
→ Azure Container Registry
→ ready for Azure Container Apps
Next, we will create the Dapr components that connect the application’s Dapr APIs to Azure Service Bus and Azure Blob Storage.
Create the Dapr Components
Now we need to connect the Dapr APIs to the Azure services we created.
In the local version, dapr init gave us default local components. The application code used component names like:
pubsub
statestore
and Dapr connected those names to local Redis-backed components.
In Azure Container Apps, we need to create those Dapr components ourselves.
We will create two Dapr components:
pubsub
→ connects Dapr pub/sub to Azure Service Bus
statestore
→ connects Dapr state management to Azure Blob Storage
The component names are important because the application code already uses them.
The Order Service publishes to:
/v1.0/publish/pubsub/OrderPlaced
The Inventory Service saves state to:
statestore
So we want the Azure version to keep the same component names:
pubsub
statestore
but point those names to Azure services instead of local Redis.
One detail can be confusing at first.
In Azure Container Apps, the YAML file describes the component settings, but the component name is supplied when you apply the YAML file.
So the pattern is:
YAML file
→ says what type of component this is
→ says how it connects to Azure
az containerapp env dapr-component set
→ gives that component its Dapr name
→ attaches it to the Container Apps environment
For example, pubsub.yaml will say:
componentType: pubsub.azure.servicebus.topics
But this command gives it the name pubsub:
--dapr-component-name pubsub
That name is what the application code uses.
Azure Container Apps Dapr components are configured at the Container Apps environment level. They can be shared by Dapr-enabled apps in that environment, and scopes can limit which app IDs load a component. ([Microsoft Learn][1])
Create the pub/sub component
First, get the Azure Service Bus connection string:
$SERVICE_BUS_CONNECTION_STRING = az servicebus namespace authorization-rule keys list `
--resource-group $RESOURCE_GROUP `
--namespace-name $SERVICE_BUS_NAMESPACE `
--name RootManageSharedAccessKey `
--query primaryConnectionString `
--output tsv
Now create a file called pubsub.yaml:
@"
componentType: pubsub.azure.servicebus.topics
version: v1
metadata:
- name: connectionString
secretRef: servicebus-connection-string
scopes:
- order-service
- inventory-service
secrets:
- name: servicebus-connection-string
value: "$SERVICE_BUS_CONNECTION_STRING"
"@ | Set-Content -Path .\pubsub.yaml
This YAML says:
This is an Azure Service Bus Topics pub/sub component.
Use this Service Bus connection string.
Only load this component for these Dapr app IDs:
order-service and inventory-service.
The component type is:
pubsub.azure.servicebus.topics
That is the Dapr component type for Azure Service Bus Topics. Dapr’s Service Bus Topics component supports connection string authentication, and connectionString is required unless you use Microsoft Entra ID authentication.
The scopes are also important:
scopes:
- order-service
- inventory-service
Those values are Dapr app IDs, not necessarily the Azure Container App resource names. In our walkthrough, we will make them match, but the thing Dapr cares about here is the Dapr application ID.
Now apply the YAML file to the Container Apps environment and name the component pubsub:
az containerapp env dapr-component set `
--name $CONTAINERAPPS_ENVIRONMENT `
--resource-group $RESOURCE_GROUP `
--dapr-component-name pubsub `
--yaml .\pubsub.yaml
This is the moment where the component receives the name pubsub.
So the full mapping is:
Application code uses:
pubsub
Azure Container Apps component name:
pubsub
Component type:
pubsub.azure.servicebus.topics
Azure backend:
Azure Service Bus
Now, when the Order Service publishes to:
/v1.0/publish/pubsub/OrderPlaced
Dapr uses the pubsub component, and that component points to Azure Service Bus.
Create the state store component
Now create the Dapr state store component.
The Inventory Service uses this component when it saves and reads inventory quantities through Dapr state management.
Create a file called statestore.yaml:
@"
componentType: state.azure.blobstorage
version: v1
metadata:
- name: accountName
value: "$STORAGE_ACCOUNT_NAME"
- name: accountKey
secretRef: storage-account-key
- name: containerName
value: "$BLOB_CONTAINER_NAME"
scopes:
- inventory-service
secrets:
- name: storage-account-key
value: "$STORAGE_ACCOUNT_KEY"
"@ | Set-Content -Path .\statestore.yaml
This YAML says:
This is an Azure Blob Storage state component.
Use this storage account and blob container.
Only load this component for the inventory-service Dapr app ID.
The component type is:
state.azure.blobstorage
That is the Dapr state store component type for Azure Blob Storage. The Azure Blob Storage state store uses metadata such as the storage account name and container name, and Azure Container Apps examples show it applied as a Dapr component named statestore.
Now apply the YAML file to the Container Apps environment and name the component statestore:
az containerapp env dapr-component set `
--name $CONTAINERAPPS_ENVIRONMENT `
--resource-group $RESOURCE_GROUP `
--dapr-component-name statestore `
--yaml .\statestore.yaml
This is the moment where the component receives the name statestore.
So the full mapping is:
Application code uses:
statestore
Azure Container Apps component name:
statestore
Component type:
state.azure.blobstorage
Azure backend:
Azure Blob Storage
Now, when the Inventory Service saves state through:
statestore
Dapr uses the statestore component, and that component points to Azure Blob Storage.
Confirm the components
List the Dapr components in the Container Apps environment:
az containerapp env dapr-component list `
--name $CONTAINERAPPS_ENVIRONMENT `
--resource-group $RESOURCE_GROUP `
--output table
You should see two components:
pubsub
statestore
At this point, the Dapr component mapping is ready:
Application code publishes to pubsub
→ Azure Service Bus handles pub/sub
Application code saves state to statestore
→ Azure Blob Storage stores the state
For this beginner walkthrough, we are using connection strings and storage keys to keep the flow understandable.
In production, managed identity and least-privilege access are usually the better Azure pattern, because they avoid long-lived keys in configuration. Azure Container Apps supports connecting Dapr APIs to Azure services through Dapr components, including identity-based options through the portal experience and supported component metadata.
Next, we will deploy the Inventory Container App with Dapr enabled so it can subscribe to OrderPlaced events and use the Azure-backed statestore component.
Deploy the Inventory Container App
Now we will deploy the Inventory service.
The Inventory service is the subscriber in this application. It needs to receive OrderPlaced events and update inventory state.
It will use two Dapr components:
pubsub
→ to subscribe to OrderPlaced events from Azure Service Bus
statestore
→ to save inventory state in Azure Blob Storage
First, create the full image name:
$INVENTORY_IMAGE = "$ACR_LOGIN_SERVER/$INVENTORY_IMAGE_NAME`:$IMAGE_TAG"
For this beginner walkthrough, we will use Azure Container Registry admin credentials so Azure Container Apps can pull the private images from ACR.
In production, managed identity is usually the better pattern, but it adds extra identity and RBAC steps. For now, registry credentials keep the deployment path focused.
Enable the ACR admin user:
az acr update `
--name $ACR_NAME `
--admin-enabled true
Get the ACR username and password:
$ACR_USERNAME = az acr credential show `
--name $ACR_NAME `
--query username `
--output tsv
$ACR_PASSWORD = az acr credential show `
--name $ACR_NAME `
--query "passwords[0].value" `
--output tsv
Now create the Inventory Container App with Dapr enabled:
az containerapp create `
--name "inventory-service" `
--resource-group $RESOURCE_GROUP `
--environment $CONTAINERAPPS_ENVIRONMENT `
--image $INVENTORY_IMAGE `
--target-port 5001 `
--ingress internal `
--registry-server $ACR_LOGIN_SERVER `
--registry-username $ACR_USERNAME `
--registry-password $ACR_PASSWORD `
--enable-dapr true `
--dapr-app-id "inventory-service" `
--dapr-app-port 5001
There are a few important parts here.
The image comes from Azure Container Registry:
--image $INVENTORY_IMAGE
The Flask app listens on port 5001, so the target port and Dapr app port both use 5001:
--target-port 5001
--dapr-app-port 5001
The app uses internal ingress:
--ingress internal
The Inventory service does not need to be public. It only needs to receive Dapr-delivered events inside the Container Apps environment.
Dapr is enabled with:
--enable-dapr true
--dapr-app-id "inventory-service"
--dapr-app-port 5001
The Dapr app ID is important because the Dapr components we created were scoped to:
inventory-service
That means this Container App can load the pubsub and statestore components that include inventory-service in their scopes.
After the command finishes, check the app:
az containerapp show `
--name "inventory-service" `
--resource-group $RESOURCE_GROUP `
--query "{Name:name, DaprEnabled:properties.configuration.dapr.enabled, DaprAppId:properties.configuration.dapr.appId, Ingress:properties.configuration.ingress.external}" `
--output table
You should see that Dapr is enabled, the app ID is inventory-service, and external ingress is not enabled.
At this point, the Inventory Container App is deployed and ready to receive OrderPlaced events through Dapr pub/sub.
Next, we will deploy the public Order Container App so we can send an order request from outside Azure.
Deploy the Order Container App
Now we will deploy the Order service.
The Order service is the public entry point for this walkthrough. It receives an order request from outside Azure and publishes an OrderPlaced event through Dapr pub/sub.
First, create the full image name:
$ORDER_IMAGE = "$ACR_LOGIN_SERVER/$ORDER_IMAGE_NAME`:$IMAGE_TAG"
Now create the Order Container App with Dapr enabled:
az containerapp create `
--name "order-service" `
--resource-group $RESOURCE_GROUP `
--environment $CONTAINERAPPS_ENVIRONMENT `
--image $ORDER_IMAGE `
--target-port 5002 `
--ingress external `
--registry-server $ACR_LOGIN_SERVER `
--registry-username $ACR_USERNAME `
--registry-password $ACR_PASSWORD `
--enable-dapr true `
--dapr-app-id "order-service" `
--dapr-app-port 5002
The Order service image also comes from Azure Container Registry:
--image $ORDER_IMAGE
The Node.js app listens on port 5002, so the target port and Dapr app port both use 5002:
--target-port 5002
--dapr-app-port 5002
This app uses external ingress:
--ingress external
That means Azure gives the Order Container App a public endpoint.
Dapr is enabled with:
--enable-dapr true
--dapr-app-id "order-service"
--dapr-app-port 5002
The Dapr app ID is:
order-service
That matters because the pubsub component was scoped to both services:
order-service
inventory-service
The Order service needs access to the pubsub component so it can publish OrderPlaced events.
After the command finishes, get the public URL:
$ORDER_FQDN = az containerapp show `
--name "order-service" `
--resource-group $RESOURCE_GROUP `
--query properties.configuration.ingress.fqdn `
--output tsv
Print the full URL:
"https://$ORDER_FQDN"
At this point, both Container Apps are deployed:
Order Container App
→ public
→ Dapr enabled
→ app ID: order-service
→ publishes OrderPlaced through pubsub
Inventory Container App
→ internal
→ Dapr enabled
→ app ID: inventory-service
→ subscribes to OrderPlaced
→ saves inventory state through statestore
Next, we will send an order request to the public Order Container App and confirm that the Inventory Container App receives and processes the event.
Test the Cloud Pub/Sub Flow
Now both Container Apps are deployed.
The Order Container App is public, so we can call it from our machine.
The Inventory Container App is internal, so we will not call it directly. Instead, we will confirm that it processed the event by checking its logs.
Send an order request to the Order Container App:
curl.exe -X POST `
-H "Content-Type: application/json" `
-d '{\"orderId\": \"order-1001\", \"itemId\": \"item1\", \"quantity\": 10}' `
"https://$ORDER_FQDN/order"
You should see a response like this:
{
"message": "OrderPlaced event published",
"event": {
"orderId": "order-1001",
"itemId": "item1",
"quantity": 10
}
}
That response confirms that the Order service received the request and published the OrderPlaced event through Dapr.
The interesting part happens behind the public endpoint.
The event flow is:
curl
→ public Order Container App
→ Order service publishes OrderPlaced through Dapr
→ Dapr pubsub component
→ Azure Service Bus
→ Inventory Container App receives the event
→ Inventory service updates state through Dapr
→ Dapr statestore component
→ Azure Blob Storage
Now check the Inventory Container App logs:
az containerapp logs show `
--name "inventory-service" `
--resource-group $RESOURCE_GROUP `
--follow
You should see a log line like this:
Processed OrderPlaced event. orderId=order-1001, itemId=item1, ordered=10, remaining=90
That proves the Inventory service received the event and reacted to it.
The Order service did not call the Inventory service directly.
It published an event to Dapr.
Dapr used the Azure Service Bus-backed pubsub component to deliver that event.
The Inventory service received the event and used the Azure Blob Storage-backed statestore component to save the updated inventory quantity.
That is the cloud version of the same Dapr pattern we ran locally.
What Changed from the Local Version
The application pattern did not change.
The Order service still publishes an OrderPlaced event through Dapr.
The Inventory service still subscribes to that event and updates inventory state through Dapr.
The important change is what sits behind the Dapr components.
In the local version, Dapr used local development components:
pubsub
→ Redis
statestore
→ Redis
In the Azure version, Dapr uses managed Azure services:
pubsub
→ Azure Service Bus
statestore
→ Azure Blob Storage
The application code still uses the same Dapr names:
pubsub
statestore
OrderPlaced
That is the key lesson.
The services do not need to be rewritten around the Azure Service Bus SDK or the Azure Storage SDK. The code continues to call Dapr APIs, and the component configuration decides which backend is used.
The hosting model also changed:
Local version
→ local processes started with dapr run
→ local Dapr sidecars
→ local Redis components
Azure version
→ Container Apps
→ Dapr enabled per app
→ Azure-backed Dapr components
So the move to Azure changed the platform, not the application pattern.
The cloud version gives us the same shape:
Order service
→ Dapr pub/sub
→ Inventory service
→ Dapr state management
But now the backing services are managed by Azure:
Azure Service Bus
→ handles event delivery
Azure Blob Storage
→ stores inventory state
Azure Container Apps
→ runs the services and Dapr sidecars
That is the main payoff of Dapr in this walkthrough:
Your code keeps talking to Dapr.
Dapr components connect that code to the infrastructure.
Clean Up Azure Resources
This walkthrough created real Azure resources.
When you are finished testing, delete the resource group so you do not leave the demo resources running.
Because all resources were created in one resource group, cleanup is simple:
az group delete `
--name $RESOURCE_GROUP `
--yes
Azure may take a few minutes to delete everything.
If you want to check whether the resource group still exists, run:
az group exists `
--name $RESOURCE_GROUP
If the result is:
false
the resource group has been removed.
This deletes the resources created for the walkthrough, including:
Azure Container Registry
Container Apps environment
Order Container App
Inventory Container App
Azure Service Bus namespace and topic
Storage account and blob container
Log Analytics workspace created with the Container Apps environment
Next, we will step back and capture the Azure Dapr mental model from what we just deployed.
The Azure Dapr Mental Model
In the local version, Dapr helped us separate application code from local infrastructure.
In this Azure version, the same idea still applies.
The application code still talks to Dapr:
Publish an event.
Subscribe to a topic.
Save state.
Azure Container Apps gives each service a managed place to run:
Order service
→ Order Container App
→ Dapr enabled
Inventory service
→ Inventory Container App
→ Dapr enabled
The Dapr components connect those services to Azure infrastructure:
pubsub component
→ Azure Service Bus
statestore component
→ Azure Blob Storage
So the complete model is:
Application container
→ Dapr sidecar
→ Dapr component
→ Azure service
For the Order service, that means:
Order service
→ Dapr pub/sub API
→ pubsub component
→ Azure Service Bus
For the Inventory service, that means:
Inventory service
→ Dapr pub/sub subscription
→ receives OrderPlaced events
Inventory service
→ Dapr state API
→ statestore component
→ Azure Blob Storage
That is the important cloud mental model.
Azure Container Apps runs the services and manages the Dapr sidecars.
Dapr gives the services consistent APIs.
Dapr components decide which Azure services sit behind those APIs.
The application stays focused on the workflow:
An order was placed.
Publish the event.
Receive the event.
Update inventory state.
The platform handles more of the hosting, sidecar, and infrastructure plumbing.
Where This Leads
You have now taken the same Dapr application through two stages.
First, you ran it locally:
Order service
→ Dapr pub/sub
→ Redis-backed local component
Inventory service
→ Dapr state management
→ Redis-backed local component
Then you moved it to Azure Container Apps:
Order Container App
→ Dapr pub/sub
→ Azure Service Bus-backed component
Inventory Container App
→ Dapr state management
→ Azure Blob Storage-backed component
The application pattern stayed the same.
The infrastructure behind Dapr changed.
That is the main lesson of this article:
Your application code talks to Dapr APIs.
Dapr components connect those APIs to the infrastructure.
The backend can change without rewriting the service around a different SDK.
This is where Dapr becomes useful in real cloud-native systems.
You can start locally with simple development components, then move to managed cloud services when the application needs a real production environment.
From here, the next improvements are mostly about production hardening:
Use managed identity instead of connection strings and keys.
Store secrets in a proper secret store.
Add private networking where needed.
Configure scaling rules.
Improve observability with logs, traces, and metrics.
Design retry and dead-letter handling for failed messages.
Automate the deployment with Bicep, Terraform, or Azure Developer CLI.
Those are important next steps, but they build on the model you now understand.
Dapr does not remove the need to design your application carefully.
It gives your services a consistent way to use distributed-system capabilities while keeping the infrastructure-specific wiring outside the main application code.