Dapr for Beginners: Running Dapr Pub/Sub Apps on Azure Container Apps

Dapr for Beginners: Running Dapr Pub/Sub Apps on Azure Container Apps

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.