Problem


In certain scenarios, you may need to restart your Azure Container Apps on schedule. Like I always joke about, a restart fixes 95% problems. However, if you do it manually, it will make you work 996. We need an automated way to complete this task. In this post, I will introduce how to use Azure Function timed trigger to automate this process. 

Solution


Azure Functions are perfect for this kind of "do something on a schedule" task. 

What You'll Need

  1. An Azure Function (Timer Trigger)
  2. A few NuGet packages to talk to Azure
  3. Your Container App's details
  4. About 30 minutes

RestartContainerApp Function

First, create an Azure Function as usual. I choose .NET 8.0 runtime. The completed code looks like this:

public class RestartContainerApp(ILoggerFactory loggerFactory)
{
    private readonly ILogger _logger = loggerFactory.CreateLogger<RestartContainerApp>();

    [Function("RestartContainerApp")]
    public async Task Run([TimerTrigger("0 0 2 * * *", RunOnStartup = true)] TimerInfo myTimer)
    {
        _logger.LogInformation($"Container App restart triggered at: {DateTime.UtcNow}");

        try
        {
            // Azure Resource details
            string subscriptionId = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
            string resourceGroupName = Environment.GetEnvironmentVariable("RESOURCE_GROUP_NAME");
            string containerAppName = Environment.GetEnvironmentVariable("CONTAINER_APP_NAME");
            string tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");

            // For local development, use AzureCliCredential with tenant
            // For production, use ManagedIdentityCredential
            TokenCredential credential = IsRunningInAzure()
                ? new ManagedIdentityCredential()
                : new AzureCliCredential(new AzureCliCredentialOptions
                {
                    TenantId = tenantId
                });

            var armClient = new ArmClient(credential);

            // Get the Container App resource
            var subscriptionResource = armClient.GetSubscriptionResource(
                new Azure.Core.ResourceIdentifier($"/subscriptions/{subscriptionId}"));

            var resourceGroup = await subscriptionResource.GetResourceGroupAsync(resourceGroupName);
            var containerApps = resourceGroup.Value.GetContainerApps();
            var containerApp = await containerApps.GetAsync(containerAppName);

            _logger.LogInformation($"Found Container App: {containerAppName}");

            // Restart the Container App by stopping and starting all revisions
            var revisions = containerApp.Value.GetContainerAppRevisions();

            await foreach (var revision in revisions)
            {
                if (revision.Data.IsActive == true)
                {
                    _logger.LogInformation($"Restarting revision: {revision.Data.Name}");

                    // Deactivate and reactivate the revision
                    await revision.DeactivateRevisionAsync();
                    await Task.Delay(5000); // Wait 5 seconds
                    await revision.ActivateRevisionAsync();

                    _logger.LogInformation($"Revision {revision.Data.Name} restarted successfully");
                }
            }

            _logger.LogInformation("Container App restart completed successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError($"Error restarting Container App: {ex.Message}");
            throw;
        }

        if (myTimer.ScheduleStatus is not null)
        {
            _logger.LogInformation("Next timer schedule at: {nextSchedule}", myTimer.ScheduleStatus.Next);
        }
    }

    private bool IsRunningInAzure()
    {
        return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID"));
    }
}

You will also need 3 NuGet packages:

dotnet add package Azure.ResourceManager
dotnet add package Azure.ResourceManager.AppContainers
dotnet add package Azure.Identity

To run it locally, set these values in local.settings.json

{
  "Values": {
    "AZURE_SUBSCRIPTION_ID": "your-sub-id",
    "AZURE_TENANT_ID": "your-tenant-id",
    "RESOURCE_GROUP_NAME": "your-rg",
    "CONTAINER_APP_NAME": "your-app-name"
  }
}

Key Points

The Timer Trigger: [TimerTrigger("0 0 2 * * *")] uses a CRON expression. It looks cryptic, but it basically says "run this at 2 AM UTC every single day." You can change it to whatever schedule you want:

0 0 */6 * * * - Every 6 hours
0 30 1 * * 1 - Every Monday at 1:30 AM
0 0 0 * * 0 - Every Sunday at midnight

Authentication: When you're testing locally, you need to use your Azure CLI credentials. But when it's deployed, it uses Managed Identity. That IsRunningInAzure() check handles both scenarios automatically.

Restart Logic: I grab all the active revisions and do the old "turn it off and on again" trick. Deactivate, wait 5 seconds, then activate again.

Deploy to Azure

Deploy your Azure Function to Azure as usual. But there are a few key steps that need your attention.

Managed Identity Authentication

First, turn on the System assigned identity in your Azure Function instance.

Then, go to the IAM settings of the resource group of your Azure Container Apps, and add a new role assignment

Add the Azure Function as a contributor role.

Application Settings

Go to the Environment variables blade of your Azure Function instance, and set those values as environment variables:

  • AZURE_SUBSCRIPTION_ID
  • AZURE_TENANT_ID
  • RESOURCE_GROUP_NAME
  • CONTAINER_APP_NAME

The Result

Now my Container App gets a nice, automated restart every night at 2 AM. I don't have to remember anything, I don't have to be at my computer!