Building Scalable Workflows with Azure Container Apps Sessions and Durable Task Scheduler

Tomer RosenthalTomer Rosenthal
7 min read

In today’s cloud-native environment, creating serverless, scalable applications that handle complex workflows with human steps and dynamic code execution is important for businesses looking for agility and reliability.

Azure Container Apps' dynamic sessions provide secure, fast, isolated environments to run arbitrary code, while Azure Durable Task Scheduler combined with the Durable Task SDK delivers a fully managed, high-performance backend for building fault-tolerant orchestrations. This blog shows how to unite these two services to create agentic workflows that can evaluate custom code with human approval while enabling you to effectively manage scalable and reliable serverless apps.


Why Use Azure Container Apps Sessions and Durable Task Scheduler?

When building workflows that execute arbitrary custom code and require human intervention, it’s important to balance security, reliability, scalability, and operational simplicity. Combining Azure Container Apps Sessions with the Durable Task Scheduler from Azure Durable Task SDK offers exactly that. Below is an in-depth breakdown of each benefit and why Durable Task Scheduler plays a crucial role:

1. Strong Isolation & Security

  • Azure Container Apps Sessions execute user-submitted code in secure Hyper-V isolated sandboxes. This isolation ensures no access to host environment or other sessions, critical when running arbitrary or untrusted code snippets.

  • Durable Task Scheduler is a managed service that provides durable execution in Azure. Durable execution is a fault-tolerant approach to running code that handles failures and interruptions through automatic retries and state persistence

2. Fast Allocation through Session Pools

  • Session pools pre-create a fleet of ready-to-go sessions in Azure Container Apps, allowing code execution requests to immediately acquire a running session.

  • Durable Task Scheduler complements this by orchestrating the session acquisition, execution, and result retrieval seamlessly.

  • This means your workflows see near-instant startup times for code evaluation phases, even when scaling to hundreds or thousands of concurrent sessions.

3. Fully Managed Durability & Reliability with Durable Task Scheduler

  • Durable Task Schedule provides first-class, stateful orchestration at scale without you having to build your own workflow or state management solution.

  • DTS persistently stores workflow state so orchestrations can survive process, VM, or regional failures without losing data.

  • It offers built-in retry policies, timeouts, and idempotency guarantees, crucial when managing workflows with unpredictable external dependencies like human interventions.

  • This durability enables you to create long-running workflows spanning hours or days with guaranteed exactly-once execution semantics.


Overview

flowchart TD
    A[User Submits Code] --> B[Orchestration Instance]
    B --> C[Execute Code in Secure Dynamic Session]
    C --> D[Receive Evaluation Results]
    D --> E[Wait for Human Intervention]
    E -->|Yes Approval| F[Continue Workflow]
    E -->|No or Timeout| G[Reject / Timeout]

The application runs in Azure Container Apps, which hosts the code that connects to both the Durable Task Scheduler and the Container Apps session pool. When a workflow starts, the code sends the customer’s script to a dynamic session in the session pool. Each session is a secure, isolated environment designed to safely run arbitrary or untrusted code via a REST API.

Meanwhile, the Durable Task Scheduler handles the orchestration of the workflow state, coordinates calling the code execution activity, waits for human approval events, manages retries, and ensures durability in case of failures or interruptions.

This separation allows you to:

  • Run custom code on demand within isolated sessions that are pre-warmed and managed by Azure Container Apps.

  • Use a fully managed Durable Task Scheduler backend to organize long-running workflows with human-in-the-loop decisions.

  • Keep the application code and orchestration logic straightforward while benefiting from secure sandboxed code execution and fault-tolerant scheduling.

Step 1: Create Azure Resources (CLI commands)

First, provision the necessary infrastructure:

# Set variables
RESOURCE_GROUP=myResourceGroup
LOCATION=westus2
CONTAINER_APP_ENV=myContainerEnv
SESSION_POOL_NAME=mySessionPool
SCHEDULER_NAME=myDurableScheduler
TASKHUB_NAME=myTaskHub
ENTRAID_USERNAME =your entra id email 

# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION

# Create Session Pool for code evaluation sessions
az containerapp sessionpool create \
  --name $SESSION_POOL_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
    --container-type PythonLTS \
    --max-sessions 100 \
    --cooldown-period 300 \
    --network-status EgressDisabled

# Store the session pool URL in a variable
# Ensure that the identity you are using has the necessary permissions to execute and create sessions. 
# If you are developing locally, this permission must be granted to your identity.
# If you are deploying in ACA, the managed identity assigned to ACA must have this permission.
SESSION_POOL_URL=$(az containerapp sessionpool show \
    --name $SESSION_POOL_NAME \
    --resource-group $RESOURCE_GROUP \
    --query 'properties.poolManagementEndpoint' -o tsv)

echo "Session Pool URL: $SESSION_POOL_URL"

# Create Durable Task Scheduler
az durabletask scheduler create \
  --name $SCHEDULER_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION

# Create Durable Task Scheduler Task Hub
az durabletask taskhub create \
 --resource-group $RESOURCE_GROUP \
 --scheduler-name $SCHEDULER_NAME \
 --name $TASKHUB_NAME

# Get the connection string for the Durable Task Scheduler
endpoint=$(az durabletask scheduler show \
 --resource-group $RESOURCE_GROUP \
 --name $SCHEDULER_NAME \
 --query 'properties.endpoint' -o tsv)

# Note that for managed identity the connection string will be different. e.g. Authentication=ManagedIdentity;ClientId=#########
DURABLE_TASK_SCHEDULER_CONNECTION_STRING="${endpoint};Authentication=DefaultAzure;TaskHub=${TASKHUB_NAME}"

DURABLE_TASK_SCHEDULER_RESOURCE_ID=$(az durabletask scheduler show \
 --resource-group $RESOURCE_GROUP \
 --name $SCHEDULER_NAME \
 --query 'id' -o tsv)


# Assign Durable Task Data Contributor role to the user
assignee=$(az ad user show --id "$ENTRAID_USERNAME" --query "id" --output tsv)
roleId="0ad04412-c4d5-4796-b79c-f76d14c8d402" # Durable Task Data Contributor
az role assignment create \
  --assignee "${assignee}" \
  --role "$roleId" \
  --scope $DURABLE_TASK_SCHEDULER_RESOURCE_ID


echo "Durable Task Scheduler Connection String: $DURABLE_TASK_SCHEDULER_CONNECTION_STRING"

# Print instructions for setting environment variables
echo ""
echo "Set the following environment variables in your development environment:"
echo "export SESSION_POOL_URL=\"$SESSION_POOL_URL\""
echo "export DURABLE_TASK_SCHEDULER_CONNECTION_STRING=\"$DURABLE_TASK_SCHEDULER_CONNECTION_STRING\""

```

### Required Resources

1. **Azure Container Apps Session Pool**:
   - Provides Python runtime for code execution
   - Configured with network egress disabled for security

2. **Azure Durable Task Scheduler**:
   - Manages the workflow orchestration
   - Requires a Task Hub for operation

## Configuration

The application requires the following environment variables:

- `SESSION_POOL_URL`: The management endpoint URL for the Azure Container Apps Session Pool
- `DURABLE_TASK_SCHEDULER_CONNECTION_STRING`: Connection string for the Durable Task Scheduler

Step 2: Building the Orchestrator using Durable SDK (C# Example)

Below is a simplified C# orchestrator that evaluates code within a session and waits for human approval.

using Microsoft.DurableTask;


[DurableTask]
class CodeExecutionOrchestrator : TaskOrchestrator<string, bool>
{

    public override async Task<bool> RunAsync(TaskOrchestrationContext context, string codeToEvaluate)
    {


        // Step 1: Execute code in dynamic session
        var submissionResult = await context.CallRunCodeInSessionAsync(codeToEvaluate);

        // Make the result available via custom status
        context.SetCustomStatus(submissionResult);

        // Create a durable timer for the timeout
        DateTime timeoutDeadline = context.CurrentUtcDateTime.AddHours(24);

        using var timeoutCts = new CancellationTokenSource();

        // Set up the timeout task that we can cancel if approval comes before timeout
        Task timeoutTask = context.CreateTimer(timeoutDeadline, timeoutCts.Token);

        // Wait for an external event (approval/rejection)
        string approvalEventName = "HumanApproval";
        Task<bool> approvalTask = context.WaitForExternalEvent<bool>(approvalEventName);

        // Wait for either the timeout or the approval response, whichever comes first
        Task completedTask = await Task.WhenAny(approvalTask, timeoutTask);

        // Step 2: Send result for human approval (external event wait)
        bool humanApproved;
        if (completedTask == approvalTask)
        {
            humanApproved = approvalTask.Result;

            // Cancel the timeout task since we got a response
            timeoutCts.Cancel();
        }
        else
        {
            // Auto-reject if timeout
            humanApproved = false;
        }

        return humanApproved;
    }
}

[DurableTask]
class RunCodeInSession : TaskActivity<string, string>
{
    private readonly CodeExecutionActivities codeExecutionActivities;

    public RunCodeInSession(CodeExecutionActivities codeExecutionActivities)
    {
        this.codeExecutionActivities = codeExecutionActivities;
    }

    public override async Task<string> RunAsync(TaskActivityContext context, string codeToEvaluate)
    {
        // Call the activity to run the code in a session
        return await codeExecutionActivities.RunCodeInSession(codeToEvaluate);
    }
}

Step 3: Activity to Execute Code in the Container Session


using System.Net.Http.Headers;
using System.Text.Json;
using Azure.Core;
using Azure.Identity;

public class CodeExecutionActivities
{
    private readonly HttpClient httpClient;

    public CodeExecutionActivities(HttpClient client)
    {
        httpClient = client;
    }

    public async Task<string> RunCodeInSession(string code)
    {

        string sessionId = Guid.NewGuid().ToString();

        string poolUrl = Environment.GetEnvironmentVariable("SESSION_POOL_URL")
            ?? throw new InvalidOperationException("Missing required environment variable 'SESSION_POOL_URL'");
        // Create a new session for code execution

        string url = $"{poolUrl}/code/execute?api-version=2024-02-02-preview&identifier={sessionId}";

        var requestBody = new
        {
            properties = new
            {
                codeInputType = "inline",
                executionType = "synchronous",
                code
            }
        };

        var requestContent = new StringContent(JsonSerializer.Serialize(requestBody));
        requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        // Add bearer token for authentication
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await GetAccessTokenAsync());

        var response = await httpClient.PostAsync(url, requestContent);
        response.EnsureSuccessStatusCode();

        var resultJson = await response.Content.ReadAsStringAsync();

        // Optionally parse and return output from resultJson
        return resultJson;
    }

    private async Task<string> GetAccessTokenAsync()
    {
        // Retrieve Azure AD token for Azure Container Apps session pool access
        var credential = new DefaultAzureCredential();
        var tokenRequestContext = new TokenRequestContext(new[] { "https://dynamicsessions.io/.default" });
        var accessToken = await credential.GetTokenAsync(tokenRequestContext);
        return accessToken.Token;
    }
}

Summary

This architecture enables scalable, durable workflows with custom code execution and human-in-the-loop approvals, leveraging Azure Container Apps Sessions and Durable Task Scheduler.

Key benefits:

  • Instant session startup via session pool

  • Guaranteed durability and fault tolerance

  • Secure, isolated environments

  • Seamless human-in-the-loop workflows

  • Fully managed, scalable backend

Code Repository

https://github.com/torosent/secure-code-evaluation

References

https://learn.microsoft.com/en-us/azure/container-apps/sessions-code-interpreter

https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler

https://github.com/Azure-Samples/Durable-Task-Scheduler

0
Subscribe to my newsletter

Read articles from Tomer Rosenthal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Tomer Rosenthal
Tomer Rosenthal

I am a seasoned software engineer and engineering manager with more than 18 years of experience in the technology sector. Over the course of my career, I have built and led high-performing teams that consistently deliver innovative solutions. I leverage a broad spectrum of cloud-native technologies to drive meaningful impact and ensure scalable, resilient architectures. Above all, I am dedicated to fostering a collaborative culture of excellence and continuous improvement within my team and organization.