Achraf Ben Alaya
No Result
View All Result
  • Home
  • News
  • Blog
    • blazor
    • c#
    • Cloud
      • Azure
    • docker
    • sql
    • xamarin
    • Dapr
    • Tricks, Tips and Fixes
    • General Tips & Fix
  • AI
  • Cloud
  • Motivation
  • Courses
  • About
    • Resume
    • Privacy Policy
SUBSCRIBE
  • Home
  • News
  • Blog
    • blazor
    • c#
    • Cloud
      • Azure
    • docker
    • sql
    • xamarin
    • Dapr
    • Tricks, Tips and Fixes
    • General Tips & Fix
  • AI
  • Cloud
  • Motivation
  • Courses
  • About
    • Resume
    • Privacy Policy
No Result
View All Result
Achraf Ben Alaya
No Result
View All Result
ADVERTISEMENT
Home Blog Cloud Azure

Building a Microservices Architecture on Azure Container Apps with Terraform Part 2

achraf by achraf
March 1, 2026
in Azure, Blog, Cloud
9 min read
0
0
SHARES
0
VIEWS
Share on FacebookShare on Twitter

In Part 1, we established a secure baseline: a VNet with NSGs, centralized logging, an Application Gateway as our public entry point, and a single frontend Container App behind an internal load balancer. Everything was locked down no direct internet access to the Container App, all traffic flowing through the Application Gateway.

Part 2 evolves that single-app deployment into a proper microservices architecture. We’re adding three things: a container registry to store our images, a backend API that only the frontend can reach, and the wiring between them.

By the end of this tutorial, the architecture looks like this:

Internet β†’ App Gateway β†’ Frontend ACA β†’ (internal FQDN) β†’ Backend ACA

                                                              ↑

                                               Only reachable inside the

                                               Container App Environment

Prerequisites

You need Part 1 deployed and running. Specifically, your Terraform state should contain these resources: `azurerm_container_app_environment.env`, `azurerm_container_app.app`, and `random_string.suffix`. If you’re starting fresh, deploy Part 1 first.

Tools required: Terraform >= 1.8.0, Azure CLI, and an Azure subscription with permissions to create container registries and container apps.

What we’re building

Three resources, each with a specific role in the Zero Trust model:

Azure Container Registry (ACR) a private registry to store container images. We’re using Standard SKU with admin access disabled. No static credentials, no shared passwords. In Part 3, we’ll wire this up with Managed Identities so our Container Apps can pull images using RBAC instead of admin keys.

Backend Container App an internal-only API service. The critical setting here is `external_enabled = false` on the ingress block. This isn’t just “not public” it means the backend has no ingress from outside the Container App Environment. Not from the VNet, not from the Application Gateway, not from the internet. Only sibling apps running in the same environment can resolve its internal FQDN.

Frontend Container App (updated) the existing frontend gets a new environment variable (`BACKEND_API_URL`) pointing to the backend’s internal FQDN. This is how the frontend discovers the backend at runtime, without hardcoding addresses.

Step 1 Create the Azure Container Registry

Create a new file called `acr.tf` in your project root:

resource "azurerm_container_registry" "acr" {

Β  name Β  Β  Β  Β  Β  Β  Β  Β = "acr${random_string.suffix.result}"

Β  resource_group_name = azurerm_resource_group.rg.name

Β  location Β  Β  Β  Β  Β  Β = azurerm_resource_group.rg.location

Β  sku Β  Β  Β  Β  Β  Β  Β  Β  = "Standard"

Β  admin_enabled Β  Β  Β  = false

}

A few things to note about this block.

The name uses the same `random_string.suffix` from Part 1, which gives us a globally unique name like `acrab3k2m`. ACR names must be 5–50 characters, alphanumeric only our 9-character name fits comfortably.

We chose Standard SKU deliberately. Basic lacks support for Private Endpoints (which we’ll need in Part 3 to lock down the registry to VNet traffic only) and has limited throughput. Premium adds geo-replication and content trust, but that’s overkill for this stage.

Setting `admin_enabled = false` is the Zero Trust play. The admin account is a single shared username/password that never rotates and can’t be scoped. By disabling it, we force all image pulls to go through Azure RBAC. In Part 3, we’ll create a Managed Identity for each Container App and assign the `AcrPull` role no credentials to store, rotate, or leak.

Step 2 Add the internal backend Container App

In your existing `aca.tf`, add the backend resource *before* the frontend (since the frontend will reference it):

resource "azurerm_container_app" "backend" {

Β  name Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  = "ca-backend-api"

Β  container_app_environment_id = azurerm_container_app_environment.env.id

Β  resource_group_name Β  Β  Β  Β  Β = azurerm_resource_group.rg.name

Β  revision_mode Β  Β  Β  Β  Β  Β  Β  Β = "Single"

Β  workload_profile_name Β  Β  Β  Β = "Consumption"

Β  template {

Β  Β  container {

Β  Β  Β  name Β  = "backend-api"

Β  Β  Β  image Β = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"

Β  Β  Β  cpu Β  Β = 0.25

Β  Β  Β  memory = "0.5Gi"

Β  Β  }

Β  Β  min_replicas = 1

Β  Β  max_replicas = 3

Β  }

Β  ingress {

Β  Β  external_enabled = false

Β  Β  target_port Β  Β  Β = 80

Β  Β  transport Β  Β  Β  Β = "auto"

Β  Β  traffic_weight {

Β  Β  Β  latest_revision = true

Β  Β  Β  percentage Β  Β  Β = 100

Β  Β  }

Β  }

}

The image is a placeholder we’re using Microsoft’s hello-world image until Part 3, when we’ll push our own images to ACR and reference them via `${azurerm_container_registry.acr.login_server}/backend:latest`.

The `external_enabled = false` line is the most important setting in this entire tutorial. When set to `false`, Azure’s control plane never provisions a public-facing load balancer rule for this app. The backend gets an internal FQDN that follows this pattern:

“`

ca-backend-api.internal.<container-app-environment-default-domain>

“`

That FQDN resolves only within the Container App Environment’s internal DNS. If you try to `curl` it from your local machine, from a VM in the same VNet, or even from the Application Gateway it won’t resolve. Only apps running inside the same Container App Environment can reach it. This is platform-level isolation, not just network-level.

Step 3 Wire the frontend to the backend

Update the existing `azurerm_container_app.app` resource in `aca.tf`. The only change is adding an `env` block inside the `container` block:

resource "azurerm_container_app" "app" {

Β  name Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  = "ca-hello-world"

Β  container_app_environment_id = azurerm_container_app_environment.env.id

Β  resource_group_name Β  Β  Β  Β  Β = azurerm_resource_group.rg.name

Β  revision_mode Β  Β  Β  Β  Β  Β  Β  Β = "Single"

Β  workload_profile_name Β  Β  Β  Β = "Consumption"

Β  template {

Β  Β  container {

Β  Β  Β  name Β  = "hello-world"

Β  Β  Β  image Β = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"

Β  Β  Β  cpu Β  Β = 0.25

Β  Β  Β  memory = "0.5Gi"

Β  Β  Β  env {

Β  Β  Β  Β  name Β = "BACKEND_API_URL"

Β  Β  Β  Β  value = "http://${azurerm_container_app.backend.ingress[0].fqdn}"

Β  Β  Β  }

Β  Β  }

Β  Β  min_replicas = 1

Β  Β  max_replicas = 3

Β  }

Β  ingress {

Β  Β  external_enabled = true

Β  Β  target_port Β  Β  Β = 80

Β  Β  transport Β  Β  Β  Β = "auto"

Β  Β  traffic_weight {

Β  Β  Β  latest_revision = true

Β  Β  Β  percentage Β  Β  Β = 100

Β  Β  }

Β  }

}

We’re using `http://` (not `https://`) for the `BACKEND_API_URL`. This is intentional. Internal ACA-to-ACA traffic within the same environment doesn’t terminate TLS at the Envoy sidecar by default. If you sent `https://`, the request would fail certificate validation because there’s no managed certificate on the internal FQDN. Use `https://` only if you’ve configured a custom domain with a managed cert on the backend.

Terraform creates an implicit dependency here: the frontend resource depends on the backend resource (because it references `azurerm_container_app.backend.ingress[0].fqdn`). This means Terraform will always create the backend first, which is exactly the ordering we want.

Step 4 Add outputs

In `main.tf`, add these two outputs after the existing ones:

output "acr_login_server" {

Β  value Β  Β  Β  = azurerm_container_registry.acr.login_server

Β  description = "ACR login server used as image prefix in container definitions"

}

output "backend_app_fqdn" {

Β  value Β  Β  Β  = azurerm_container_app.backend.ingress[0].fqdn

Β  description = "Internal FQDN of the Backend Container App (resolvable only within the CAE)"

}

The `acr_login_server` output gives you the hostname (e.g., `acrab3k2m.azurecr.io`) that you’ll use in Part 3 to tag and push images. The `backend_app_fqdn` output is useful for debugging you can verify the internal FQDN pattern and confirm it matches what the frontend receives in its environment variable.

Step 5 Plan and apply

terraform plan -out=tfplan

terraform apply tfplan

Expected changes: 2 new resources (ACR + backend ACA), 1 modified resource (frontend ACA with new env var). The Container App Environment, Application Gateway, and all networking resources remain unchanged.

After apply, verify the outputs:

terraform output acr_login_server

terraform output backend_app_fqdn

The `backend_app_fqdn` should look something like `ca-backend-api.internal.cae-internal-demo.westeurope.azurecontainerapps.io`.

## Updated architecture

Here’s how the architecture looks after Part 2:

What changed from Part 1

ComponentPart 1Part 2
🧩 Container Apps1 (frontend only)2 (frontend + internal backend)
πŸ“¦ Container RegistryNoneStandard SKU, admin disabled
βš™οΈ Frontend ConfigStatic image, no env varsBACKEND_API_URL injected
πŸ”’ Backend IngressN/Aexternal_enabled = false (internal only)
πŸ“€ Outputs5 (gateway IP, frontend FQDN, URL, static IP, DNS zone)7 (+ ACR login server, backend FQDN)
πŸ“ New Filesβ€”acr.tf

Security posture

Part 2 adds two important Zero Trust controls.

First, the backend is platform-isolated. Setting `external_enabled = false` isn’t the same as putting a deny rule in an NSG. NSG rules operate at the network layer and can be misconfigured, have priority conflicts, or get accidentally modified. The `external_enabled = false` setting is enforced by the Azure Container Apps control plane it never provisions the external load balancer rule. There’s no network path to misconfigure.

Second, the ACR has no admin credentials. There’s no username/password to steal, share, or accidentally commit to a repo. When we connect ACR to the Container Apps in Part 3, it will be through Azure RBAC and Managed Identities ephemeral, automatically rotated, least-privilege tokens.

What’s next in Part 3

Part 3 will close the loop with CI/CD and identity:

– Managed Identities : System-assigned identities on each Container App, with `AcrPull` role assignments to pull images from ACR without any credentials.

– GitHub Actions : CI/CD pipelines that build, push to ACR, and update Container App revisions.

– Full automation : From `git push` to production deployment with no manual steps.

The foundation we’ve built in Parts 1 and 2 internal networking, platform-level isolation, RBAC-ready registry makes Part 3 a matter of wiring, not rearchitecting.

This is Part 2 of a full series on building production-ready microservices on Azure Container Apps with Terraform. Part 1 covers the secure networking baseline.

Repo : achrafbenalaya/azure-workshop-aca

ShareTweet
Previous Post

Β 2025 – Certifications, Community, and 50K Views

Related Posts

Blog

Β 2025 – Certifications, Community, and 50K Views

December 28, 2025
83
Azure

From Manual Terraform to AI-Assisted DevOps: Building an Azure Container Platform (Part 1)

December 23, 2025
161
AI

Build and Host an Expense Tracking MCP Server with Azure Functions

November 2, 2025
777
Azure

Log Analytics Workspace Chaos: How We Tamed 100+ Orphaned Workspaces

October 17, 2025
284
Azure

Honored to be recognized as a Microsoft Azure MVP for 2025-2026

July 20, 2025
148
AI

Model Context Protocol (MCP): The Future of AI Integration

April 21, 2025
353

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Terraform

Certifications

Microsoft certified trainer (MCT)

Recommended

Reading Excel file in Azure Web Apps

Reading Excel file in Azure Web Apps

December 1, 2020
1.9k
Win free certifications at the Microsoft Build Cloud Skills Challenge | May 2022 🎁

Win free certifications at the Microsoft Build Cloud Skills Challenge | May 2022 🎁

May 28, 2022
293
How to SSH into AKS Nodes

How to SSH into AKS Nodes

May 11, 2021
7.9k
Part 5-A : Using Azure DevOps, Automate Your CI/CD Pipeline and Your Deployments

Part 5-B : Using Azure DevOps, Automate Your CI/CD Pipeline and Your Deployments

April 20, 2023
652
Create a Linux VM with infrastructure in Azure using Terraform

Create a Linux VM with infrastructure in Azure using Terraform

August 30, 2020
2.4k
DevOps : Deploy infrastructure using Terraform and Azure DevOps pipelines

DevOps : Deploy infrastructure using Terraform and Azure DevOps pipelines

June 2, 2021
4.7k
Facebook Twitter LinkedIn Youtube

Building a Microservices Architecture on Azure Container Apps with Terraform Part 2

March 1, 2026

Β 2025 – Certifications, Community, and 50K Views

December 28, 2025

From Manual Terraform to AI-Assisted DevOps: Building an Azure Container Platform (Part 1)

December 23, 2025

Categories

  • AI (3)
  • Apps (1)
  • Azure (68)
  • blazor (2)
  • Blog (95)
  • c# (7)
  • Cloud (70)
  • Courses (4)
  • Dapr (4)
  • docker (4)
  • Games (1)
  • General Tips & Fix (1)
  • Home (1)
  • Kubernetes Service (AKS) (1)
  • motivation (2)
  • Motivation (3)
  • News (9)
  • Resume (1)
  • sql (4)
  • Terrafrom (2)
  • Tricks, Tips and Fixes (4)
  • xamarin (5)
No Result
View All Result
  • Home
  • News
  • Blog
    • blazor
    • c#
    • Cloud
      • Azure
    • docker
    • sql
    • xamarin
    • Dapr
    • Tricks, Tips and Fixes
    • General Tips & Fix
  • AI
  • Cloud
  • Motivation
  • Courses
  • About
    • Resume
    • Privacy Policy