Why Azure
The cloud choice explained: every Azure object GMI runs on, its scaling limits, how the platform grows with the cloud — and the honest admission that AWS or Google Cloud could host this system too
flowchart LR
subgraph T[Today ~$175/mo: 300-500 users]
A1[App Services: Basic]
A2[Functions: B-series]
A3[MySQL: Burstable<br/>171 connections]
A4[Worker VM: B-series]
end
subgraph S1[First step ~$240/mo: 1,500-2,500 users]
B1[MySQL SKU step:<br/>connections + memory double]
B2[Functions/App SKU step]
end
subgraph S2[Beyond: same shape, bigger dials]
C1[Read replicas + caching]
C2[More worker VMs on<br/>the same job queue]
C3[CDN in front of<br/>static surfaces]
end
T --> S1 --> S2
T -. architecture unchanged<br/>at every step .-> S2The Choice, Honestly Stated
GMI chose Azure for a simple, unromantic reason: this is a Microsoft-stack system — .NET 10 everywhere, Blazor on the web, MAUI on mobile, Entra ID for admin identity, Azure DevOps for CI/CD (see Why Microsoft Technology). Azure is where that stack has first-party citizenship: Functions run .NET isolated workers natively, App Service understands ASP.NET deployment idioms, Entra integrates without federation gymnastics, and one identity spans code, pipelines, and cloud.
But honesty over dogma, as everywhere on this site: GMI is not architecturally married to Azure. The platform is plain .NET, MySQL, blob storage, and Linux processes — the most portable combination in modern computing. AWS or Google Cloud could host this system with equivalent objects (the matrix below maps them one-to-one), and the infrastructure-as-code layer (Pulumi) is itself multi-cloud. We went with Azure because the stack pointed there — not because the doors are locked.
Every Azure Object, Its Limits, and Its Dial
| Azure object | Role in GMI | Today's limit | The growth dial |
|---|---|---|---|
| App Service ×3 (Basic, Windows) | Client Web (WASM + static), Admin Web, Technology site | Static serving — practically traffic-indifferent at this tier | SKU up (S/P tiers), instances out, CDN in front as a bolt-on |
| Functions App (Linux plan, B-series) | The 336-endpoint API + share/card rendering | Plan compute; stateless workers | Scale up or out linearly; Premium plan for pre-warmed instances if latency ever demands it |
| MySQL Flexible Server (Burstable) | All 69 tables + job queues — the system of record | 171 connections — the platform's first scaling wall | SKU steps double connections/memory; General Purpose tier; read replicas for read-heavy load. Watched first because it scales hardest |
| Linux VM (B-series) | The 15-service worker fleet: reports, ML, SEO/card builds, billing | vCPU/RAM bound on batch throughput | Bigger VM for heavier jobs, or more VMs polling the same DB job queue — horizontal by design |
| Blob Storage ×2 (Standard LRS + ZRS) | Reports, raw data, generated pages, social cards | None that matter — petabyte-class | The dial is a credit card, not a decision |
| Key Vault | Secrets: API keys, connection strings, OAuth | Not a scaling concern | — |
| Entra ID | Admin identity + RBAC | Not a scaling concern | — |
| VNet + NSGs | Network isolation for VM and Functions | Not a scaling concern | — |
| Application Insights | Telemetry, dependencies, exceptions | Ingestion-priced | Sampling already on; caps configurable |
| Azure DevOps | Repos + the CI/CD pipeline for every app | One hosted build lane | Parallel-job purchase if build queue ever matters |
The through-line: compute and database have dials; identity, secrets, and networking are set-and-forget; storage is unlimited. Capacity today is ~300–500 users at ~$175/month, with the first SKU step (~$240/month) reaching an estimated 1,500–2,500 — full math on the Lean Cloud Economics page.
The Same System on AWS or Google Cloud — the Portability Matrix
Could GMI run on the other hyperscalers? Yes — object for object. This matrix is the honest proof that the architecture, not the vendor, is the asset:
| Role | Azure (chosen) | AWS equivalent | Google Cloud equivalent |
|---|---|---|---|
| Web app hosting | App Service | Elastic Beanstalk / App Runner | Cloud Run / App Engine |
| API compute | Azure Functions (isolated .NET) | Lambda + API Gateway | Cloud Run / Cloud Functions |
| Relational database | MySQL Flexible Server | RDS for MySQL / Aurora MySQL | Cloud SQL for MySQL |
| Worker fleet | Linux VM (systemd services) | EC2 | Compute Engine |
| Object storage | Blob Storage | S3 | Cloud Storage |
| Secrets | Key Vault | Secrets Manager | Secret Manager |
| Admin identity | Entra ID | IAM Identity Center / Cognito | Cloud Identity / Identity Platform |
| Telemetry | Application Insights | CloudWatch + X-Ray | Cloud Monitoring / Trace |
| Networking | VNet + NSGs | VPC + Security Groups | VPC + Firewall Rules |
| CI/CD | Azure DevOps Pipelines | CodePipeline / GitHub Actions | Cloud Build / GitHub Actions |
| Infrastructure-as-code | Pulumi — already multi-cloud; the same C# IaC style targets all three | ||
What would actually change in a migration: connection strings, the identity integration for the admin portal, and pipeline YAML. What wouldn't: the 312K lines of application code, the schema, the job-queue pattern, or the architecture. That's the payoff of building on boring, portable primitives — the cloud is a supplier, not a landlord. Azure earns the business by fitting the stack best; it keeps the business the same way.