Appearance
Infrastructure Technology Reference
Detailed reference for every Azure service and infrastructure component used in the Microtec ERP platform.
Infrastructure as Code
Azure Bicep
Role: Declarative IaC DSL — all Azure resources are defined and deployed via Bicep
Scope: Subscription-scoped (main.bicep) — creates resource groups and all child resources in a single deployment
Location: Devops/azure/infrastructure/
Module structure:
infrastructure/
├── main.bicep # Subscription entrypoint
├── modules/
│ ├── containerEnv.bicep # Container Apps Environment
│ ├── containerApp.bicep # Individual Container App
│ ├── keyVault.bicep # Key Vault + RBAC assignments
│ ├── serviceBus.bicep # Service Bus namespace + queues/topics
│ ├── redis.bicep # Azure Managed Redis
│ ├── sql.bicep # Azure SQL Server + databases
│ ├── acr.bicep # Azure Container Registry
│ ├── staticWebApp.bicep # Frontend SWA
│ ├── frontDoor.bicep # AFD profile + origins
│ └── vnet.bicep # VNet + subnets + NSGs
└── parameters/
├── dev.bicepparam
├── stage.bicepparam
├── preprod.bicepparam
├── uat.bicepparam
└── prod.bicepparamDeploy command:
bash
# Full infrastructure deploy for dev
az deployment sub create \
--location "uksouth" \
--template-file Devops/azure/infrastructure/main.bicep \
--parameters @Devops/azure/infrastructure/parameters/dev.bicepparamParameter generation: Build-BicepParams.ps1 reads services-config.json and outputs .bicepparam files — no hand-editing of parameter files.
Container Runtime
Azure Container Apps (ACA)
SKU: Consumption tier (pay-per-use serverless)
Role: Managed Kubernetes-based container runtime — all backend microservices
Region: UK South (uksouth) for all environments
Two-CAE pattern per environment:
| CAE | Internet-facing | Services | mTLS |
|---|---|---|---|
| Public CAE | Yes (via AFD) | Gateway.API, Keycloak | No |
| Private CAE | No — VNet-internal | All other 12+ services | Yes (enforced) |
Key ACA features used:
| Feature | Configuration |
|---|---|
| Managed identity | System-assigned — ACR pull, Key Vault read |
| VNet integration | Private CAE is subnet-joined (no public IP) |
| KEDA autoscaling | HTTP, CPU, memory, and cron triggers |
| Revision management | Traffic splitting for zero-downtime deploy |
| Health probes | Liveness (/health/live) + readiness (/health/ready) |
| Secret references | KV references via keyvaultref: syntax |
| Dapr | Not used |
KEDA scaling configuration (from services-config.json):
| Trigger type | Metric | Default config |
|---|---|---|
http | Concurrent requests per replica | 100 |
cpu | CPU utilisation % | 70 |
memory | Memory utilisation % | 75 |
cron | Time schedule | Business-hours pre-warm |
Resource allocation:
| Service tier | CPU | Memory | Min replicas | Max replicas |
|---|---|---|---|---|
| Gateway (prod) | 1.0 | 2Gi | 2 | 10 |
| Keycloak (prod) | 2.0 | 4Gi | 2 | 6 |
| AppsPortal (prod) | 0.5 | 1Gi | 1 | 8 |
| Other services (prod) | 0.25 | 0.5Gi | 1 | 5 |
| All services (dev) | 0.25 | 0.5Gi | 0 | 3 |
Scale-to-zero in dev
Dev environment Container Apps scale to 0 replicas after 5 minutes of inactivity, eliminating idle costs. Cold start latency is ~3–5 seconds.
Container Registry
Azure Container Registry (ACR)
SKU: Basic (dev/stage) / Standard (preprod/uat/prod)
Role: Private Docker image registry for all backend service images
Authentication: Pipeline uses $(System.AccessToken) built-in identity; Container Apps use managed identity for pull
ACR names per environment:
| Environment | Backend ACR | Frontend ACR |
|---|---|---|
| dev | micerpbedevacr | micerpfrdevacr |
| stage | micerpbestageacr | micerpfrstageacr |
| preprod | micerpbepreprodacr | micerpfrpreprodacr |
| uat | micerpbeuatacr | micerpfruatacr |
| production | micerpbeproductionacr | micerpfrproductionacr |
Naming formula: replace('mic-erp-be-{env}acr', '-', '') (all alphanumeric — Azure Storage naming rules)
Image tag strategy:
| Tag | Lifecycle |
|---|---|
$(Build.BuildId) | Created on every pipeline run — permanent |
latest | Overwritten on every successful dev build |
stable | Set manually post production validation |
Geo-replication: Production ACR is geo-replicated to West Europe for disaster recovery.
Relational Database
Azure SQL (SQL Server 2022 compatible)
SKU: General Purpose, 4 vCores (prod) / Basic, 5 DTU (dev/stage)
Role: Primary relational database for all tenant and admin data
Server naming: mic-backend-shared-sql-rg (shared across environments — do NOT rename)
Shared SQL Server
The SQL Server VM (20.50.120.95) is shared across non-production environments. Tenant databases are isolated by database name. Do not apply environment-destructive commands to the shared server.
Database-per-tenant isolation:
| Database type | Naming | Purpose |
|---|---|---|
| Admin DB | MicrotecAdmin | Cross-tenant metadata, tenant registry |
| Tenant DB | Tenant_{tenantId} | All tenant-specific ERP data |
| Hangfire DB | Microtec_Hangfire | Background job storage |
Connection string management: All connection strings are stored in Key Vault. Never in appsettings.json or pipeline variables in plain text.
SSH access (emergency):
bash
ssh -i ~/.ssh/mic-shared-sql sqladmin@20.50.120.95Cache
Azure Cache for Redis
SKU: C1 Standard (1 GB, dev/stage/preprod/uat) / C2 Standard (6 GB, prod)
Version: Redis 6.x (managed by Azure)
TLS: Enabled — port 10000 (SSL)
Auth: Password stored in Key Vault → RedisConfiguration--Password
Stage Redis endpoint: mic-erp-be-stage-redis.uksouth.redis.azure.net:10000
Usage patterns:
| Pattern | Implementation |
|---|---|
| Output cache | [OutputCache] attribute on controller actions |
| Distributed cache | IDistributedCache with GetStringAsync/SetStringAsync |
| Rate limiting | Redis sliding window counter per client IP |
| Session data | Auth tokens and user context (TTL 30 min) |
| Tenant config cache | Tenant settings — TTL 1 hour |
Local dev: Redis 7.x runs as Docker container on port 6379 (no TLS).
Messaging
Azure Service Bus
SKU: Standard tier (supports topics, subscriptions, dead-letter queues, 256KB messages)
Role: Production async message broker for all inter-service events
MassTransit transport: Configured automatically in cloud environments via IServiceBusConfiguration
Topology:
| Queue / Topic | Type | Producers | Consumers |
|---|---|---|---|
erp-events | Topic | AppsPortal, HR, Inventory | Notification, Workflow, Worker |
notification-requests | Queue | Any service | Notification.Apis |
zatca-submissions | Queue | AppsPortal | Integration.Apis |
import-jobs | Queue | AppsPortal | Import.Apis |
report-generation | Queue | Any service | Reporting.Apis |
Dead-letter queues: Enabled for all queues — messages failing after 5 delivery attempts land in {queue}/$deadLetterQueue.
Local dev: RabbitMQ 3.13.x (Docker) on ports 5672 (AMQP) / 15672 (management UI). MassTransit swaps transport transparently.
Secrets Management
Azure Key Vault
SKU: Standard
Role: Central secrets store — all credentials, connection strings, API keys, certificates
Authentication: System-assigned managed identity on each Container App — no credentials stored in pipeline variables
KV names (inconsistent across envs — use exact names):
| Environment | Key Vault Name |
|---|---|
| dev | mic-erp-be-dev-skv |
| stage | mic-erp-stg-kv |
| preprod | mic-erp-be-preprod-skv |
| uat | mic-erp-uat-kv |
| production | (contact platform team) |
No naming formula for Key Vault
Key Vault names are not derived from a formula — they evolved organically. Always use the exact names in the table above.
Secret naming convention:
ASP.NET Core: ConnectionStrings:DefaultConnection
Key Vault name: ConnectionStrings--DefaultConnection (double-dash replaces colon)
Reference in CAE environment variable:
keyvaultref:https://{kv-name}.vault.azure.net/secrets/ConnectionStrings--DefaultConnectionGlobal Load Balancing & CDN
Azure Front Door (Standard)
SKU: Standard (WAF, CDN, custom domains, health probes)
Role: Global entry point — TLS termination, WAF, CDN for static assets, routing to backend and frontend origins
Origin groups:
| Origin group | Origin | Routes |
|---|---|---|
backend-origins | Public CAE — Gateway.API | /api/*, /auth/* |
frontend-origins | Azure Static Web Apps | /* (catch-all) |
WAF policies:
- OWASP Core Rule Set 3.2 (prevention mode in prod, detection in dev/stage)
- Custom rules: block requests from restricted countries, rate-limit
/api/auth/*
TLS: Auto-managed certificates — Azure-managed certificate per custom domain. No manual renewal.
Health probe: Every 30 seconds to /health/ready on each origin. Unhealthy origins removed from rotation automatically.
Custom domains by environment:
| Environment | Domain |
|---|---|
| dev | microtec-test.com |
| stage | microtecstage.com |
| uat | microtec-uat.com |
| production | onlinemicrotec.com.sa |
Frontend Hosting
Azure Static Web Apps (SWA)
SKU: Standard
Role: Hosts all Angular micro-frontend apps — global CDN delivery, built-in auth integration
Deployment: Azure DevOps pipeline uploads dist/ output via AzureStaticWebApp@0 task
SWA per app (10 total per environment):
| App | SWA Name pattern |
|---|---|
| erp-home | mic-erp-fr-{env}-home-swa |
| apps-accounting | mic-erp-fr-{env}-accounting-swa |
| apps-hr | mic-erp-fr-{env}-hr-swa |
| apps-finance | mic-erp-fr-{env}-finance-swa |
| apps-sales | mic-erp-fr-{env}-sales-swa |
| apps-purchase | mic-erp-fr-{env}-purchase-swa |
| apps-inventory | mic-erp-fr-{env}-inventory-swa |
| app-distribution | mic-erp-fr-{env}-distribution-swa |
| fixed-assets | mic-erp-fr-{env}-fixed-assets-swa |
| bussiness-owners | mic-erp-fr-{env}-bo-swa |
SWA routing: staticwebapp.config.json configures SPA fallback ("navigationFallback") and CORS headers for Module Federation cross-origin remoteEntry.js loading.
Networking
Azure Virtual Network
SKU: Standard
Role: Isolates each environment's Container Apps from the internet and from each other
Topology: Per-environment VNet — no peering between environments
CIDR allocation:
| Environment | VNet CIDR | Public Subnet | Private Subnet | PE Subnet |
|---|---|---|---|---|
| dev | 10.0.0.0/16 | 10.0.0.0/24 | 10.0.1.0/24 | 10.0.2.0/24 |
| stage | 10.1.0.0/16 | 10.1.0.0/24 | 10.1.1.0/24 | 10.1.2.0/24 |
| preprod | 10.6.0.0/16 | 10.6.0.0/24 | 10.6.1.0/24 | 10.6.2.0/24 |
| uat | 10.5.0.0/16 | 10.5.0.0/24 | 10.5.1.0/24 | 10.5.2.0/24 |
| production | 10.2.0.0/16 | 10.2.0.0/24 | 10.2.1.0/24 | 10.2.2.0/24 |
| shared-sql | 10.100.0.0/16 | N/A | N/A | N/A |
Private endpoints: SQL, Redis, Service Bus, Key Vault, ACR, and Blob Storage all communicate with Container Apps exclusively over private endpoints — no public internet exposure.
Document / Object Storage
Azure Blob Storage
SKU: Standard LRS (dev/stage) / Standard ZRS (prod)
Role: Attachment storage (invoices, documents, profile photos)
Account naming: micerpbedevsa, micerpfrstageacr (alphanumeric, no dashes)
Access: Blob SAS tokens generated on-demand by Attachment.Apis (never expose account keys)
Document Database
MongoDB 7.0 (Non-Production)
Deployment: VM-based Docker container on shared infrastructure
Ports: 27017–27020
Use case: Workflow engine state storage, flexible document data in non-prod environments
MongoDB init bug
MONGO_INITDB_ROOT_* environment variables create a broken SCRAM auth in MongoDB 7.0. Workaround: start container with no auth, run db.updateUser(), then restart with auth enabled. This is a known gotcha — do not "fix" it.
Azure Cosmos DB (Production)
API: MongoDB-compatible
Role: Production document store — replaces MongoDB VM in production
Consistency: Session consistency (default)
Monitoring & APM
Application Insights
SKU: Log Analytics workspace-based
Role: APM, request tracing, exception tracking, custom metrics, availability tests
Integration: OpenTelemetry → Azure Monitor exporter (auto-configured by Microtec.Web.Hosting)
Retention: 90 days (cloud default)
Azure Monitor
Role: Central metrics and alerting platform
Alert types configured:
- Container App replica count > max (scaling bottleneck)
- HTTP 5xx rate > 1% (error rate alert)
- SQL DTU utilisation > 80% (scale trigger)
- Redis cache miss rate > 20% (cache effectiveness alert)