Skip to content

Container Apps Architecture

All Microtec ERP backend services run as Azure Container Apps. The environment is split into a Public CAE (internet-facing) and a Private CAE (VNet-internal only), with mTLS enforced between all private services.


Public vs Private CAE

PropertyPublic CAEPrivate CAE
Internet ingressYes (via AFD)No
VNet integrationYesYes
CAE Namemic-erp-be-{env}-cae-publicmic-erp-be-{env}-cae-private
Resource Groupmic-erp-be-{env}-apps-public-rgmic-erp-be-{env}-apps-private-rg
ServicesGateway.API, KeycloakAll 11 internal services
mTLSIngress terminatesEnforced between all apps
External IPVia AFD static IPsNone

VNet Integration

Both CAEs are integrated into the same VNet using dedicated subnets:

SubnetCIDRPurpose
public-apps10.x.0.0/24Public CAE infrastructure
private-apps10.x.1.0/24Private CAE infrastructure
data10.x.2.0/24Redis, Service Bus private endpoints
keyvault10.x.3.0/24Key Vault private endpoint

The /24 subnet provides 256 addresses. Azure Container Apps requires a minimum /23 subnet for the infrastructure, but the actual application containers communicate via the CAE's internal overlay network.

Subnet Sizing

If you need to add more than ~20 container app revisions simultaneously, consider upgrading to /23. Each CAE revision consumes IPs from the infrastructure subnet.


Service Catalogue

All 13 services with their CAE placement:

ServiceCAE TypeMin ReplicasMax ReplicasCPUMemory
Gateway.APIPublic150.51Gi
KeycloakPublic131.02Gi
AppsPortal.ApisPrivate1100.51Gi
Inventory.ApisPrivate050.51Gi
BusinessOwners.ApisPrivate050.51Gi
BusinessOwners.AdminPortalPrivate050.51Gi
Integration.ApisPrivate050.51Gi
Attachment.ApisPrivate150.250.5Gi
Notification.ApisPrivate130.250.5Gi
Workflows.ApisPrivate130.51Gi
Hr.Personnel.ApisPrivate050.51Gi
Template.BlazorPrivate050.51Gi
Platforms.WorkerPrivate130.51Gi

Zero Min Replicas

Module services (HR, Finance, Sales, etc.) can scale to zero in dev/stage to save costs. The Gateway routes warm them up on first request. Allow 10–20 seconds cold start time for scaled-zero services.


mTLS Configuration

mTLS is enabled at the Private CAE level. All service-to-service communication within the private environment uses mutual TLS:

The certificates are managed by the CAE platform (auto-rotated). Services do not manage TLS certificates manually.

bicep
// container-apps.bicep - Private CAE configuration
resource privateCae 'Microsoft.App/managedEnvironments@2023-05-01' = {
  name: '${resourcePrefix}-cae-private'
  properties: {
    vnetConfiguration: {
      infrastructureSubnetId: privateAppsSubnetId
      internal: true  // No public IP
    }
    peerAuthentication: {
      mtls: {
        enabled: true  // Enforce mTLS
      }
    }
  }
}

KEDA Scaling Triggers

Container Apps use KEDA (Kubernetes-based Event Driven Autoscaler) for scaling. Each service configures one or more triggers:

HTTP Scaling (most services)

bicep
scale: {
  minReplicas: 0
  maxReplicas: 10
  rules: [
    {
      name: 'http-scale'
      http: {
        metadata: {
          concurrentRequests: '50'
        }
      }
    }
  ]
}

CPU/Memory Scaling (heavy services)

bicep
scale: {
  minReplicas: 1
  maxReplicas: 5
  rules: [
    {
      name: 'cpu-scale'
      custom: {
        type: 'cpu'
        metadata: {
          type: 'Utilization'
          value: '70'
        }
      }
    }
  ]
}

Cron Warmup (prod only)

For production, a cron trigger pre-warms all services at 6:00 AM GST (02:00 UTC) to avoid cold starts during business hours:

bicep
{
  name: 'cron-warmup'
  custom: {
    type: 'cron'
    metadata: {
      timezone: 'Asia/Riyadh'
      start: '0 6 * * 1-6'   // 6 AM, Sun-Fri (Gulf work week)
      end: '0 22 * * 1-6'    // 10 PM
      desiredReplicas: '1'
    }
  }
}

Service Bus Scaling (Notification service)

bicep
{
  name: 'servicebus-scale'
  custom: {
    type: 'azure-servicebus'
    metadata: {
      queueName: 'notifications'
      messageCount: '10'
    }
    auth: [
      {
        secretRef: 'servicebus-connection'
        triggerParameter: 'connection'
      }
    ]
  }
}

Service Discovery

Within the same CAE, container apps discover each other by name:

http://{container-app-name}

Examples:

http://accounting          → Accounting service
http://notification        → Notification service
http://workflow            → Workflow service

The Gateway.API uses the private CAE's internal DNS. No service mesh or Consul required — CAE provides built-in service discovery within an environment.

Cross-CAE Communication

The Gateway (Public CAE) calls private services using the private CAE's internal FQDN:

https://accounting.internal.{env}.azurecontainerapps.io

This traffic stays within the VNet and uses mTLS.


Health Probes

Each container app defines both liveness and readiness probes:

Liveness Probe

Checks if the container should be restarted:

bicep
probes: [
  {
    type: 'Liveness'
    httpGet: {
      path: '/health/live'
      port: 8080
    }
    initialDelaySeconds: 30
    periodSeconds: 10
    failureThreshold: 3
  }
]

Readiness Probe

Controls whether traffic is routed to a replica:

bicep
{
  type: 'Readiness'
  httpGet: {
    path: '/health/ready'
    port: 8080
  }
  initialDelaySeconds: 15
  periodSeconds: 5
  failureThreshold: 5
}

Probe Endpoints (.NET)

All backend services expose these endpoints via ASP.NET Core health checks:

EndpointPurpose
/health/liveBasic liveness (process is running)
/health/readyReadiness (DB connected, dependencies healthy)
/healthFull health report (Seq/monitoring)

Container Image Pull

Images are pulled from the environment's dedicated ACR using a user-assigned managed identity:

bicep
registries: [
  {
    server: '${acrName}.azurecr.io'
    identity: managedIdentityId
  }
]

Image tag strategy:

  • Dev/Stage: {branch-name}-{build-id} (e.g., feature-invoice-1234)
  • PrepProd/UAT: {version}-rc{n} (e.g., 1.5.0-rc2)
  • Production: {version} (e.g., 1.5.0)

Internal Documentation — Microtec Platform Team