Edit

Share via


Host a Durable Functions app in Azure Container Apps (.NET isolated)

Azure Functions provides integrated support for developing, deploying, and managing containerized Function Apps on Azure Container Apps. Use Azure Container Apps for your Functions apps when you need to run in the same environment as other microservices, APIs, websites, workflows, or any container hosted programs. Learn more about running Azure Functions in Container Apps.

Note

While Durable Functions supports several storage providers or backends, autoscaling apps hosted in Azure Container Apps is only available with the Microsoft SQL (MSSQL) backend. If another backend is used, you have to set minimum replica count to greater than zero.

In this article, you learn how to:

  • Create a Docker image from a local Durable Functions project.
  • Create an Azure Container App and related resources.
  • Deploy the image to the Azure Container App and set up authentication.

Prerequisites

Create a local Durable Functions project

In Visual Studio Code, create a .NET isolated Durable Functions project configured to use the MSSQL backend.

Test the app locally and return to this article.

Create a Dockerfile in the project root that describes the minimum required environment to run the function app in a container.

  1. In the project root directory, create a new file named Dockerfile.

  2. Copy/paste the following content into the Dockerfile.

    FROM mcr.microsoft.com/dotnet/sdk:8.0 AS installer-env
    
    COPY . /src/dotnet-function-app
    RUN cd /src/dotnet-function-app && \
    mkdir -p /home/site/wwwroot && \
    dotnet publish *.csproj --output /home/site/wwwroot
    
    # To enable ssh & remote debugging on app service change the base image to the one below
    # FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-appservice
    FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0
    ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
        AzureFunctionsJobHost__Logging__Console__IsEnabled=true
    
    COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]
    
  3. Save the file.

  4. Add a .dockerignore file with the following content:

    local.settings.json
    
  5. Save the .dockerignore file.

Build the container image

Build the Docker image. Find the complete list of supported base images for Azure Functions in the Azure Functions Base by Microsoft | Docker Hub

  1. Start the Docker daemon.

  2. Sign in to Docker with the docker login command.

  3. When prompted, log in with your username and password. A "Login Succeeded" message confirms that you're signed in.

  4. Navigate to your project root directory.

  5. Run the following command to build the image, replacing <DOCKER_ID> with your Docker Hub account ID:

    dockerId=<DOCKER_ID>
    imageName=IMAGE_NAME>
    imageVersion=v1.0.0
    
    docker build --tag $dockerId/$imageName:$imageVersion .
    

    Note

    If you're running on an M-series Mac, use --platform linux/amd64 instead.

  6. Push the image to Docker:

    docker push $dockerId/$imageName:$imageVersion
    

    Depending on network speed, the initial image push might take a few minutes. While you're waiting, proceed to the next section.

Create Azure resources

Create the Azure resources necessary for running Durable Functions on a container app.

  • Azure resource group: Resource group containing all created resources.
  • Azure Container App environment: Environment hosting the container app.
  • Azure Container App: Image containing the Durable Functions app is deployed to this app.
  • Azure Storage Account: Required by the function app to store app-related data, such as application code.

Initial set up

  1. In a new terminal, log in to your Azure subscription:

    az login  
    
    az account set -s <subscription_name>
    
  2. Run the required commands to set up the Azure Container Apps CLI extension:

    az upgrade
    
    az extension add --name containerapp --upgrade
    
    az provider register --namespace Microsoft.App
    
    az provider register --namespace Microsoft.OperationalInsights
    

A workload profile determines the amount of compute and memory resources available to the container apps deployed in an environment. Create a Consumption workload profile for scale-to-zero support and pay-per-use.

  1. Set the environment variables.

    location=<REGION>
    resourceGroup=<RESOURCE_GROUP_NAME>
    storage=<STORAGE_NAME>
    containerAppEnv=<CONTAINER_APP_ENVIRONMNET_NAME>
    functionApp=<APP_NAME>
    vnet=<VNET_NAME>
    
  2. Create a resource group.

    az group create --name $resourceGroup --location $location
    
  3. Create the container app environment.

    az containerapp env create \
      --enable-workload-profiles \
      --resource-group $resourceGroup \
      --name $containerAppEnv \
      --location $location \
    
  4. Create a container app based on the Durable Functions image.

    az containerapp create --resource-group $resourceGroup \
    --name $functionApp \
    --environment $containerAppEnv \
    --image $dockerId/$imageName:$imageVersion \
    --ingress external \
    --kind functionapp \
    --query properties.outputs.fqdn
    
  5. Make note of the app URL, which should look similar to https://<APP_NAME>.<ENVIRONMENT_IDENTIFIER>.<REGION>.azurecontainerapps.io.

Create databases

  1. Create an Azure Storage account, which is required by the function app.

    az storage account create --name $storage --location $location --resource-group $resourceGroup --sku Standard_LRS
    
  2. In the Azure portal, create an Azure SQL database to persist state information. During creation:

    • Enable Azure services and resources to access this server (under Networking)
    • Set the value for Database collation (under Additional settings) to Latin1_General_100_BIN2_UTF8.

Note

Refrain from enabling the Allow Azure services and resources to access this server setting for production scenarios. Production applications should implement more secure approaches, such as stronger firewall restrictions or virtual network configurations.

Configure identity-based authentication

Managed identities make your app more secure by eliminating secrets from your app, such as credentials in the connection strings. While you can choose between system-assigned and user-assigned managed identity, user-assigned managed identity is recommended, as it's not tied to the app lifecycle.

In this section, you set up user-assigned managed identity for Azure Storage.

  1. Set the environment variables.

    subscription=<SUBSCRIPTION_ID>
    identity=<IDENTITY_NAME>
    
  2. Create a managed identity resource.

    echo "Creating $identity"
    az identity create -g $resourceGroup -n $identity --location "$location"
    
  3. Assign the user identity to the container app.

    echo "Assigning $identity to app"
    az containerapp identity assign --resource-group $resourceGroup --name $functionApp --user-assigned $identity
    
  4. Set the scope of the role-based access control (RBAC) permissions.

    scope="/subscriptions/$subscription/resourceGroups/$resourceGroup/providers/Microsoft.Storage/storageAccounts/$storage"
    
  5. Get the user identity's clientId.

    # Get the identity's ClientId 
    clientId=$(az identity show --name $identity --resource-group $resourceGroup --query 'clientId' --output tsv)
    
  6. Assign the role Storage Blob Data Owner role for access to the storage account.

    echo "Assign Storage Blob Data Owner role to identity"
    az role assignment create --assignee "$clientId" --role "Storage Blob Data Owner" --scope "$scope"
    

Set up app settings

Note

Authenticating to the MSSQL database using managed identity isn't supported when hosting a Durable Functions app in Azure Container Apps. For now, this guide authenticates using connection strings.

  1. From the SQL database resource in the Azure portal, navigate to Settings > Connection strings to find the connection string.

    Screenshot showing database connection string.

    The connection string should have a format similar to:

    dbserver=<SQL_SERVER_NAME>
    sqlDB=<SQL_DB_NAME>
    username=<DB_USER_LOGIN>
    password=<DB_USER_PASSWORD>
    
    connStr="Server=tcp:$dbserver.database.windows.net,1433;Initial Catalog=$sqlDB;Persist Security Info=False;User ID=$username;Password=$password;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
    

    If you forget the password from the previous database creation step, you can reset it on the SQL server resource.

    Screenshot showing reset password button.

  2. Store the SQL database's connection string as a secret called sqldbconnection in the container app.

    az containerapp secret set \
    --resource-group $resourceGroup \
    --name $functionApp \
    --secrets sqldbconnection=$connStr
    
  3. Add the following settings to the app:

    az containerapp update \
    -n $functionApp \
    -g $resourceGroup \
    --set-env-vars SQLDB_Connection=secretref:sqldbconnection \
    AzureWebJobsStorage__accountName=$storage \
    AzureWebJobsStorage__clientId=$clientId \
    AzureWebJobsStorage__credential=managedidentity \
    FUNCTIONS_WORKER_RUNTIME=dotnet-isolated
    

Test locally

  1. Use an HTTP test tool to send a POST request to the HTTP trigger endpoint, which should be similar to:

    https://<APP NAME>.<ENVIRONMENT_IDENTIFIER>.<REGION>.azurecontainerapps.io/api/DurableFunctionsOrchestrationCSharp1_HttpStart
    

    The response is the HTTP function's initial result letting you know that the Durable Functions orchestration started successfully. While the response includes a few useful URLs, it doesn't yet display the orchestration's end result.

  2. Copy/paste the URL value for statusQueryGetUri into your browser's address bar and execute. Alternatively, you can continue to use the HTTP test tool to issue the GET request.

    The request queries the orchestration instance for the status. You should see that the instance finished and the outputs or results of the Durable Functions app.

    {
        "name":"HelloCities",
        "instanceId":"7f99f9474a6641438e5c7169b7ecb3f2",
        "runtimeStatus":"Completed",
        "input":null,
        "customStatus":null,
        "output":"Hello, Tokyo! Hello, London! Hello, Seattle!",
        "createdTime":"2023-01-31T18:48:49Z",
        "lastUpdatedTime":"2023-01-31T18:48:56Z"
    }
    

Next steps

Learn more about: