Terraform Deployment Patterns for Azure Container Apps

Azure Container Apps occupies a deliberate middle ground in the Azure container portfolio. The service delivers container-based deployment without the operational overhead of managing a full Kubernetes cluster, while exposing capabilities such as auto-scaling, traffic splitting, and Dapr integration. Terraform is used to codify the creation of both the shared environment and the individual applications that run inside it. The reference material describes two complementary implementation paths: a Terraform module that wraps deployment of a container app with explicit support for log analytics workspace parameters, and hands-on examples that combine the AzureRM provider with the AzAPI provider to provision Azure Container Apps when native AzureRM resource support is limited.

The architecture that underpins all Terraform work is composed of two main resources. The Container Apps Environment is the shared environment where apps run. It is described in the material as the cluster equivalent, providing networking, logging, and a shared Dapr configuration. The Container App is an individual application running within the environment. All Terraform configuration must first ensure the environment exists and then reference its identity when creating apps. This separation drives how variables, locals, and for_each expressions are structured in Terraform code.

Terraform Module Characteristics for Azure Container Apps

The module referenced at https://github.com/Azure/terraform-azure-container-apps is presented as a Terraform module to deploy a container app in Azure with the following characteristics.

The module provides the ability to specify all the parameters of log analytics workspace resource. In practice this means the caller can pass through workspace SKU, retention, zone redundancy, and other workspace-level settings without leaving the module boundary. The impact for users is that observability is defined alongside the application rather than as a subsequent manual step. Centralized logging configuration becomes versioned with the infrastructure, which reduces configuration drift between environments.

The module allows the container app image to be specified using image parameter in template block under container_apps variable. The template block is the location where the application container definition lives. Placing image under template aligns with the Azure Container Apps resource model where template contains containers, scaling, and probes. For consumers of the module this provides a single named entry point for the image reference and keeps image definition close to other container attributes.

For multiple apps, the module specifies the container parameters under containers. The shift from a singular container definition to a containers collection enables one Terraform module invocation to emit several container apps. Operators can model a portfolio of services with a single variable map, which reduces duplication and makes for_each driven deployment feasible.

Architecture Overview of Azure Container Apps

Azure Container Apps sits in the sweet spot between Azure App Service and Azure Kubernetes Service. You get container-based deployment without managing Kubernetes clusters, along with features like auto-scaling, traffic splitting, and Dapr integration. If you want to run containers without the operational overhead of AKS, Container Apps is your best bet.

The material explicitly covers creating Azure Container Apps environments and apps with Terraform, setting up ingress, configuring auto-scaling, and handling secrets. These four concerns are interdependent. Environment creation establishes the networking and logging surface. App creation binds to that environment. Ingress controls external reachability. Auto-scaling governs replica behavior. Secrets management isolates sensitive values from the Terraform state.

Architecture Overview

Azure Container Apps has two main resources:

Resource Role Analogy
Container Apps Environment Shared environment where apps run. Provides networking, logging, shared Dapr configuration The cluster
Container App Individual application running within the environment The workload

The environment acts as the boundary for isolation, policy, and observability. The app is the unit of deployment. Terraform must first create the environment resource and then reference its identifier in each app resource.

AzureRM Provider Based Container App Definition

One reference provides a concrete Terraform example using the azurerm provider for azurermcontainerapp.

The example defines a simple web API container app.

resource "azurerm_container_app" "api" { name = "ca-api-prod" container_app_environment_id = azurerm_container_app_environment.main.id resource_group_name = azurerm_resource_group.apps.name revision_mode = "Single" template { container { name = "api" image = "myregistry.azurecr.io/api:v1.0.0" cpu = 0.5 memory = "1Gi" env { name = "ASPNETCORE_ENVIRONMENT" value = "Production" } env { name = "LOG_LEVEL" value = "Information" } env { name = "DATABASE_URL" secret_name = "database-url" } liveness_probe { transport = "HTTP" path = "/healthz" port = 8080 } readiness_probe { transport = "HTTP" path = "/ready" port = 8080 } } min_replicas = 1 max_replicas = 10 } ingress { external_enabled = true target_port = 8080 traffic_weight { percentage = 100 latest_revision = true } } secret { name = "database-url" value = var.database_url } tags = { application = "api" environment = "production" } }

Revision mode controls how new revisions are deployed. Single means new revisions immediately replace the old one. Multiple means multiple revisions can run simultaneously, allowing traffic splitting. The choice directly impacts deployment safety and rollback capability. Single is simpler and consumes fewer resources. Multiple enables canary and blue-green patterns at the platform level.

Using Azure Container Registry is described as the most common production pattern for image pull. The example image myregistry.azurecr.io/api:v1.0.0 illustrates registry-based sourcing. The material notes that most production setups pull images from Azure Container Registry.

AzAPI Provider Integration for Container Apps

Although we can provision and manage Azure Container Apps with Project Bicep, people keep on asking how to manage their Azure Container Apps using HashiCorp Terraform. This article demonstrates how to provision Azure Container Apps with Terraform utilizing a combination of the well-known AzureRM provider (azurerm) and the recently released AzAPI provider (azapi).

What is the AzAPI provider

The recently released AzAPI provider for Terraform is a thin abstraction layer on top of the Azure ARM REST API. It allows you to use any API version provided by Azure's ARM REST API, which means you can use the latest entities and their properties, including entities in public and private previews. The AzAPI provider also supports the same authentication mechanisms as the AzureRM provider.

The impact is immediate for teams needing preview features. When AzureRM lags behind ARM API availability, AzAPI provides a path to provision resources without waiting for provider releases. The thin abstraction preserves Terraform state management while exposing the full ARM schema.

The material notes that luckily, the new AzAPI provider brings support for all Azure ARM API entities. We can finally provision Azure Container Apps with Terraform by using both providers. I use the AzAPI provider for way more use-cases than just ACA. It’s super handy, and with the corresponding VS Code extension, it’s super convenient to describe all parts of a bigger infrastructure.

Bootstrap the project with AzureRM and AzAPI providers is presented as the first step. Authenticate using a Service Principal is called out explicitly. Service Principal authentication provides non-interactive CI/CD access and satisfies least privilege requirements for production pipelines.

Create Locals and Variables follows. Specify Resource Group and Log Analytics Workspace with AzureRM is the next step. Using AzureRM for foundational resources leverages mature provider support for resource groups and monitoring.

Hands-on: Use the AzAPI provider is the pivot point. Provision Azure Container Apps with Terraform is the goal.

The example AzAPI resource uses foreach over the containerapps variable.

resource "azapi_resource" "aca" { for_each = { for ca in var.container_apps: ca.name => ca} type = "Microsoft.App/containerApps@2022-03-01" parent_id = azurerm_resource_group.rg.id location = azurerm_resource_group.rg.location name = each.value.name body = jsonencode({ properties: { managedEnvironmentId = azapi_resource.aca_env.id configuration = { ingress = { external = each.value.ingress_enabled targetPort = each.value.ingress_enabled?each.value.containerPort: null } } template = { containers = [ { name = "main" image = "${each.value.image}:${each.value.tag}" resources = { cpu = each.value.cpu_requests memory = each.value.mem_requests } } ] scale = { minReplicas = each.value.min_replicas maxReplicas = each.value.max_replicas } } } }) tags = local.tags }

The type is Microsoft.App/containerApps@2022-03-01. The parentid is set to the AzureRM resource group. The body encodes properties including managedEnvironmentId, configuration ingress, template containers, and scale. The foreach expression iterates over var.container_apps, enabling declarative multi-app definitions.

Having all resources in place, the workflow is to execute terraform apply. Terraform CLI will present the execution plan that outlines which resources will be created in Azure. At this point, we have to confirm the plan, and we’ll see Terraform provisioning our infrastructure in Azure.

As soon as provision has finished you can again use Azure CLI and grab the FQDNs for the newly provisioned container apps.

az containerapp list -g rg-aca-terraform \ --query="[].{FQDN:properties.configuration.ingress.fqdn}" \ -otable

The output shows FQDNs such as herogopher.wonderfulbeach-762ce195.westeurope.azurecontainerapps.io and devilgopher.wonderfulbeach-762ce195.westeurope.azurecontainerapps.io. These values confirm ingress is active and provide the endpoint for validation.

Provisioning Workflow and Operational Steps

The material outlines a sequence of steps for a complete project.

  • Bootstrap the project with AzureRM and AzAPI providers
  • Authenticate using an Service Principal
  • Create Locals and Variables
  • Specify Resource Group and Log Analytics Workspace with AzureRM
  • Hands-on: Use the AzAPI provider
  • Provision Azure Container Apps with Terraform
  • Bootstrap the project with

What we’ve covered in this article is summarized as:

  • Understand what the AzAPI provider is and when it’s an excellent addition to the AzureRM provider
  • Authenticated with Azure using a Service Principal
  • Described all infrastructure components using Terraform and leveraged language features such as variables, locals, and expressions (for_each)
  • Optionally used the VS Code extension for AzAPI to drive productivity and get precise code completion
  • Provisioned Azure Container Apps with Terraform

The conclusion notes it’s a bummer that AzureRM providers still lack support for Azure Container Apps. Luckily, the new AzAPI provider brings support for all Azure ARM API entities.

Multi-App Deployment Patterns and Scaling Considerations

For multiple apps, specifying container parameters under containers allows a single Terraform module to render many apps. The foreach pattern used with AzAPI maps each entry in var.containerapps to a distinct azapi_resource. This pattern scales horizontally without copy-paste.

Scaling configuration is expressed through minReplicas and maxReplicas in the template. The AzureRM example sets minreplicas = 1 and maxreplicas = 10. The AzAPI example sets minReplicas and maxReplicas from each.value. These values drive the platform auto-scaler.

Ingress configuration is expressed via externalenabled and targetport in AzureRM, and via external and targetPort in the AzAPI body. The conditional targetPort = each.value.ingress_enabled?each.value.containerPort: null shows how Terraform expressions can gate ingress properties.

Secrets are handled differently between providers. AzureRM uses a secret block with name and value. AzAPI embeds secrets within the ARM body. Both approaches keep sensitive values out of the container image definition.

Operational Verification

After terraform apply completes, verification uses Azure CLI. Listing FQDNs confirms public ingress. The query extracts properties.configuration.ingress.fqdn for each app in the resource group. This step closes the loop between declarative Terraform and runtime observability.

Conclusion

The reference material establishes that Terraform management of Azure Container Apps currently relies on a hybrid provider strategy. AzureRM covers foundational resources such as resource groups and log analytics workspaces, while AzAPI provides access to the ARM API for Container Apps entities that are not yet natively supported. The AzAPI provider functions as a thin abstraction over ARM REST, enabling use of any API version and preview properties, and supports the same authentication mechanisms as AzureRM.

The Container Apps Environment remains the central shared construct, providing networking, logging, and Dapr configuration. Container Apps are deployed into that environment with template-driven container definitions, scaling policies, ingress settings, and secret references. Revision mode choice between Single and Multiple directly influences deployment safety and traffic management capability.

Module-based approaches expose log analytics workspace parameters and allow image specification via template block under containerapps variable, with container parameters under containers for multi-app scenarios. Foreach driven AzAPI resources enable portfolio scale deployment from a single variable map, with managedEnvironmentId linking apps to their environment.

Authentication via Service Principal, use of variables and locals, and expression driven configuration provide a maintainable Terraform codebase. Post-deployment verification via Azure CLI FQDN listing confirms successful ingress provisioning.

The overall pattern is a deliberate split: stable AzureRM resources for platform foundations, AzAPI for cutting-edge Container Apps resources, with Terraform expressions and module parameters providing repeatable, scalable deployment of container workloads in Azure.

Sources

  1. terraform-azure-container-apps
  2. how-to-create-azure-container-apps-in-terraform
  3. deploy-azure-container-apps-with-terraform

Related Posts