Appearance
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
| Property | Public CAE | Private CAE |
|---|---|---|
| Internet ingress | Yes (via AFD) | No |
| VNet integration | Yes | Yes |
| CAE Name | mic-erp-be-{env}-cae-public | mic-erp-be-{env}-cae-private |
| Resource Group | mic-erp-be-{env}-apps-public-rg | mic-erp-be-{env}-apps-private-rg |
| Services | Gateway.API, Keycloak | All 11 internal services |
| mTLS | Ingress terminates | Enforced between all apps |
| External IP | Via AFD static IPs | None |
VNet Integration
Both CAEs are integrated into the same VNet using dedicated subnets:
| Subnet | CIDR | Purpose |
|---|---|---|
public-apps | 10.x.0.0/24 | Public CAE infrastructure |
private-apps | 10.x.1.0/24 | Private CAE infrastructure |
data | 10.x.2.0/24 | Redis, Service Bus private endpoints |
keyvault | 10.x.3.0/24 | Key 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:
| Service | CAE Type | Min Replicas | Max Replicas | CPU | Memory |
|---|---|---|---|---|---|
| Gateway.API | Public | 1 | 5 | 0.5 | 1Gi |
| Keycloak | Public | 1 | 3 | 1.0 | 2Gi |
| AppsPortal.Apis | Private | 1 | 10 | 0.5 | 1Gi |
| Inventory.Apis | Private | 0 | 5 | 0.5 | 1Gi |
| BusinessOwners.Apis | Private | 0 | 5 | 0.5 | 1Gi |
| BusinessOwners.AdminPortal | Private | 0 | 5 | 0.5 | 1Gi |
| Integration.Apis | Private | 0 | 5 | 0.5 | 1Gi |
| Attachment.Apis | Private | 1 | 5 | 0.25 | 0.5Gi |
| Notification.Apis | Private | 1 | 3 | 0.25 | 0.5Gi |
| Workflows.Apis | Private | 1 | 3 | 0.5 | 1Gi |
| Hr.Personnel.Apis | Private | 0 | 5 | 0.5 | 1Gi |
| Template.Blazor | Private | 0 | 5 | 0.5 | 1Gi |
| Platforms.Worker | Private | 1 | 3 | 0.5 | 1Gi |
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 serviceThe 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.ioThis 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:
| Endpoint | Purpose |
|---|---|
/health/live | Basic liveness (process is running) |
/health/ready | Readiness (DB connected, dependencies healthy) |
/health | Full 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)
Related Documentation
- Infrastructure Overview — Bicep structure, managed identities
- Key Vault — Secret injection into container apps
- Naming Conventions — CAE and container app naming
- Services Config — Per-service scaling configuration