Migrating .NET Workloads to Azure - App Service, AKS, and Landing Zone Setup
Migrating .NET applications to Azure looks straightforward on the surface - it’s Microsoft’s ecosystem, after all. In practice, organisations hit the same problems: no subscription structure, poorly designed networking, costs spiralling out of control after the first month. This guide describes a process that minimises those risks.
Why Azure for .NET Workloads
Azure is the natural home for .NET applications for several technical and business reasons:
- Native .NET integration - App Service, Azure Functions, and AKS have full support for .NET 8/9. Hot reload, diagnostics, profiling - everything works immediately without configuring additional agents.
- Azure Hybrid Benefit - if you have Windows Server or SQL Server licences with Software Assurance, you can use them in Azure at no additional cost. For a typical organisation with 10 Windows servers, that’s 40-60% savings on compute.
- Entra ID (Azure AD) - if your organisation uses Microsoft 365, identities already exist. SSO to internal applications works immediately without building a separate IAM system.
- European regions - multiple datacentres across Europe (West Europe, North Europe, plus country-specific regions). For applications requiring EU data residency, Azure provides comprehensive coverage.
- Compliance - ISO 27001, SOC 2, GDPR compliance documentation available through Azure Compliance Manager for European regions.
This doesn’t mean Azure is the only option. If your application is a pure containerised API with no Microsoft ecosystem dependencies - AWS and GCP are excellent choices too. But if you have .NET + SQL Server + Active Directory + Microsoft licences - Azure provides the shortest path to the cloud.
Landing Zone - the Foundation Before Migration
A Landing Zone is a pre-configured Azure environment with correct management structure, networking, security, and policies. Without it, you’re migrating into chaos.
What Is an Azure Landing Zone
A Landing Zone is not a product you buy - it’s Microsoft’s reference architecture (Cloud Adoption Framework) that defines:
- Management Group and subscription structure
- Network topology (hub-spoke or Virtual WAN)
- Governance policies (Azure Policy)
- Baseline security services (Microsoft Defender for Cloud, Key Vault)
- Monitoring (Log Analytics Workspace, Azure Monitor)
Without a Landing Zone, the typical scenario looks like this: a dev team creates a subscription, deploys an application, and after 3 months nobody knows who’s paying for what, networking is flat (everything can see everything), and security policies don’t exist.
Subscription Structure
The minimum sensible structure for an organisation migrating its first application:
Tenant Root Group
└── Organisation (Management Group)
├── Platform
│ ├── sub-connectivity (hub VNet, DNS, firewall)
│ └── sub-management (Log Analytics, Automation)
└── Workloads
├── sub-prod (production)
└── sub-nonprod (dev + staging)
Why separate subscriptions?
- Cost isolation - you can see how much production costs vs. dev without tagging
- Permission isolation - developers get Contributor on nonprod, Reader on prod
- Limits - each subscription has separate quotas (vCPU, public IPs)
- Budget alerts - separate thresholds per subscription
For an organisation of 20-50 people with one application - 4 subscriptions is the minimum. For larger organisations with multiple teams - one subscription per workload per environment.
Networking - Hub-Spoke Topology
Hub-spoke is the simplest topology that provides isolation and central control:
- Hub VNet (sub-connectivity) - central point: Azure Firewall or NVA, VPN Gateway to on-premise, DNS Private Resolver
- Spoke VNet (sub-prod, sub-nonprod) - application networks, peered with the hub
┌─────────────────────────────────────────────┐
│ Hub VNet (10.0.0.0/16) │
│ ├── AzureFirewallSubnet (10.0.1.0/24) │
│ ├── GatewaySubnet (10.0.2.0/24) │
│ └── DNSResolverSubnet (10.0.3.0/24) │
└─────────────┬───────────────┬───────────────┘
│ peering │ peering
┌─────────────▼──────┐ ┌────▼──────────────┐
│ Spoke-Prod │ │ Spoke-NonProd │
│ (10.1.0.0/16) │ │ (10.2.0.0/16) │
│ ├── AppSubnet │ │ ├── AppSubnet │
│ ├── DataSubnet │ │ ├── DataSubnet │
│ └── AKSSubnet │ │ └── AKSSubnet │
└────────────────────┘ └────────────────────┘
Key decisions:
- Azure Firewall vs NVA - Azure Firewall is a managed service (~$900/month base cost). For smaller organisations, NSG + Route Tables without a firewall is sufficient. Add the firewall when you have compliance requirements for egress traffic inspection.
- DNS - Azure Private DNS Zones for PaaS services (Private Endpoints). If you have AD DS on-premise - DNS forwarding to on-prem DNS via Private Resolver.
- VPN vs ExpressRoute - VPN Gateway (~$140/month) for traffic <1 Gbps. ExpressRoute (~$200/month + port fee from provider) for dedicated connection and lower latency.
App Service vs AKS - When to Use Which
This is the most common question when migrating .NET to Azure. The answer depends on application complexity, operational requirements, and team expertise.
App Service - When It’s Enough
App Service is a fully managed PaaS platform. You don’t manage servers, don’t configure the OS, don’t worry about patches. You deploy code or a container - Azure handles the rest.
Ideal for:
- Monolithic .NET applications (Web API + frontend)
- Simple microservices (up to ~5-10 services)
- Teams without Kubernetes experience
- Applications with predictable traffic
- MVPs and early migration phases (lift-and-shift)
Limitations:
- Max 30 instances per App Service Plan (horizontal scaling)
- No control over OS and runtime beyond what the platform offers
- Networking - VNet Integration works but with limitations (no inbound private endpoint below Premium v3)
- Costs scale non-linearly at large scale
Typical .NET architecture on App Service:
Internet → Application Gateway (WAF)
→ App Service (Web API)
→ Azure SQL Database
→ Azure Cache for Redis
→ Azure Service Bus
→ Azure Blob Storage
Estimated cost (production, West Europe):
| Component | SKU | Cost/month |
|---|---|---|
| App Service Plan | P1v3 (2 vCPU, 8 GB RAM) | ~$140 |
| Azure SQL Database | S2 (50 DTU) | ~$75 |
| Application Gateway | WAF v2 (basic) | ~$250 |
| Redis Cache | C1 Basic (1 GB) | ~$40 |
| Blob Storage | LRS Hot (100 GB) | ~$2 |
| Total | ~$507/mo |
AKS (Azure Kubernetes Service) - When You Need It
AKS is managed Kubernetes. The control plane is free - you pay for nodes (VMs) and additional services. It gives full control but requires operational knowledge.
Ideal for:
- Microservices architecture (>10 services)
- Applications requiring advanced scaling (HPA, KEDA, node autoscaler)
- Teams with Kubernetes experience
- Applications requiring sidecar patterns (service mesh, observability)
- Migrations from on-premise Kubernetes or Docker Swarm
Limitations:
- Learning curve - Kubernetes is a complex system
- Operational overhead - upgrades, node pool management, networking (CNI)
- Minimum sensible production configuration costs more than App Service
- You need an engineer who understands K8s at an operational level
Typical .NET architecture on AKS:
Internet → Azure Front Door (WAF + CDN)
→ AKS Ingress Controller (NGINX or AGIC)
→ Pod: Web API (.NET 8)
→ Pod: Background Worker
→ Pod: gRPC Service
→ Azure SQL Database (Private Endpoint)
→ Azure Service Bus
→ Azure Key Vault (CSI Secret Store)
Estimated cost (production, West Europe, 3-node cluster):
| Component | SKU | Cost/month |
|---|---|---|
| AKS Control Plane | Free tier | $0 |
| Node Pool (system) | 3× Standard_D2s_v5 (2 vCPU, 8 GB) | ~$210 |
| Node Pool (applications) | 2× Standard_D4s_v5 (4 vCPU, 16 GB) | ~$280 |
| Azure SQL Database | S2 (50 DTU) | ~$75 |
| Azure Front Door | Standard | ~$35 + traffic |
| Container Registry | Basic | ~$5 |
| Key Vault | Standard | ~$1 |
| Total | ~$606/mo |
Decision Matrix
| Criterion | App Service | AKS |
|---|---|---|
| Number of microservices | 1-10 | 10+ |
| Team Kubernetes experience | Not required | Required |
| Time-to-production | 1-2 weeks | 3-6 weeks |
| Scaling | Up to 30 instances | Virtually unlimited |
| Entry cost (production) | ~$150/mo | ~$500/mo |
| Operational overhead | Low | Medium-high |
| Vendor lock-in | High (PaaS-specific) | Low (K8s portability) |
| On-prem K8s compatibility | None | Full |
Our recommendation: If you don’t have a strong reason to use AKS - start with App Service. Migrating from App Service to AKS later is simpler than trying to learn Kubernetes during a production migration. Add AKS when App Service becomes a limitation (>10 services, need for service mesh, complex scaling requirements).
Step-by-Step Migration Process
Phase 1: Assessment (1-2 weeks)
Before you move anything, you need to know what you have:
Inventory:
- List of applications and their dependencies (databases, queues, cache, files)
- .NET Framework vs .NET Core/.NET 8 versions (critical - .NET Framework requires Windows containers or Windows App Service)
- Latency requirements (does the application need to be close to users in a specific region?)
- Current traffic patterns (peak hours, seasonality)
- Dependencies on on-premise services (AD DS, file shares, legacy APIs)
Assessment tools:
- Azure Migrate - discovery agent installed on-premise, scans VMs, maps dependencies, suggests Azure sizing
- Azure App Service Migration Assistant - checks IIS/.NET application compatibility with App Service
- .NET Upgrade Assistant - estimates effort required to migrate from .NET Framework to .NET 8
Phase output:
- Decision: App Service vs AKS (per application)
- Migration strategy: lift-and-shift vs re-platform vs re-architect
- Preliminary Azure budget (target monthly cost)
- Blocker list (e.g., application requires .NET Framework 4.5 + Windows-specific APIs)
Phase 2: Landing Zone Setup (1-2 weeks)
With Terraform or Bicep - never manually. Infrastructure must be repeatable.
Minimum to get started:
# Terraform - module structure example
module "management_groups" {
source = "./modules/management-groups"
}
module "connectivity" {
source = "./modules/connectivity"
hub_address_space = "10.0.0.0/16"
dns_servers = ["10.0.3.4"]
vpn_gateway_sku = "VpnGw1"
location = "westeurope"
}
module "workload_prod" {
source = "./modules/spoke"
spoke_address_space = "10.1.0.0/16"
hub_vnet_id = module.connectivity.hub_vnet_id
environment = "prod"
location = "westeurope"
}
Landing Zone checklist:
- ☐ Management Groups and subscriptions created
- ☐ Hub VNet with DNS configured
- ☐ Spoke VNets with peering to hub
- ☐ Azure Policy assigned (allowed locations)
- ☐ Central Log Analytics Workspace
- ☐ Key Vault per environment
- ☐ RBAC - role assignments for teams
- ☐ Budget alerts ($/month per subscription)
- ☐ Microsoft Defender for Cloud enabled (at least Free tier)
Phase 3: Database Migration (1-3 weeks)
The database migrates first - it’s the riskiest element and requires the most testing.
SQL Server → Azure SQL Database:
The most common path for .NET applications. Azure SQL Database is fully managed PaaS - backups, patches, HA (99.99% SLA) handled by Azure.
Steps:
- Compatibility check - Azure SQL Database doesn’t support all SQL Server features (no SQL Agent, no cross-database queries, no linked servers). Use Data Migration Assistant to identify issues.
- Model selection - DTU (simple, predictable cost) vs vCore (flexible, Azure Hybrid Benefit). For migrations, start with DTU S2/S3, then optimise.
- Data migration - Azure Database Migration Service (DMS) for online migration (minimal downtime) or bacpac export/import for offline.
- Private Endpoint - database accessible only from your VNet, not from the public internet.
- Connection string update - change in application from
Server=on-prem-sqltoServer=tcp:mydb.database.windows.net,1433.
# Example - online migration via DMS (Azure CLI)
az dms project task create \
--resource-group rg-migration \
--service-name dms-prod \
--project-name sql-migration \
--task-name migrate-appdb \
--source-connection-json '{
"dataSource": "10.0.1.50",
"authentication": "SqlAuthentication",
"userName": "migration_user",
"password": "***"
}' \
--target-connection-json '{
"dataSource": "appdb-prod.database.windows.net",
"authentication": "SqlAuthentication",
"userName": "sqladmin",
"password": "***"
}' \
--database-options-json '[{
"name": "AppDatabase",
"targetDatabaseName": "AppDatabase"
}]'
Important: If you have SQL Server Enterprise licences with SA - use the vCore model with Azure Hybrid Benefit. Savings reach 55% vs full price.
Phase 4: Application Deployment (1-2 weeks)
App Service Path
For .NET 8 applications on Linux (recommended):
# 1. Create App Service Plan
az appservice plan create \
--name asp-app-prod \
--resource-group rg-app-prod \
--sku P1v3 \
--is-linux \
--location westeurope
# 2. Create Web App
az webapp create \
--name app-myapi-prod \
--resource-group rg-app-prod \
--plan asp-app-prod \
--runtime "DOTNETCORE:8.0"
# 3. Configure VNet Integration
az webapp vnet-integration add \
--name app-myapi-prod \
--resource-group rg-app-prod \
--vnet spoke-prod-vnet \
--subnet app-subnet
# 4. Set connection string (Key Vault reference)
az webapp config appsettings set \
--name app-myapi-prod \
--resource-group rg-app-prod \
--settings "ConnectionStrings__Default=@Microsoft.KeyVault(VaultName=kv-app-prod;SecretName=sql-connection-string)"
Key production configurations:
- Always On = true - prevents cold start after 20 minutes of inactivity
- Health Check -
/healthendpoint checked every 30 seconds, unhealthy instances removed from the load balancer - Deployment Slots - staging slot for blue-green deployment (swap without downtime)
- Managed Identity - system-assigned identity for accessing Key Vault, SQL, and Storage without secrets in connection strings
AKS Path
For microservices architecture:
# Kubernetes deployment - .NET 8 API
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-orders
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-orders
template:
metadata:
labels:
app: api-orders
azure.workload.identity/use: "true"
spec:
serviceAccountName: sa-api-orders
containers:
- name: api
image: acrmyapp.azurecr.io/api-orders:1.2.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef:
name: sql-connection
key: connection-string
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
Key AKS elements for .NET:
- Workload Identity - managed identity for pods (instead of static secrets). Configured through federated credentials in Entra ID.
- KEDA - pod scaling based on external metrics (Service Bus queue length, Event Hub message count)
- Azure CNI Overlay - networking for large clusters without exhausting IP addresses in the subnet
- Azure Key Vault CSI Driver - mounting secrets from Key Vault directly as volumes in pods
CI/CD - Deployment Automation
Migration without CI/CD means rewriting yourself into manual deployments in a new environment. The pipeline should be ready in parallel with infrastructure.
For App Service (GitHub Actions):
name: Deploy to App Service
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- run: dotnet publish -c Release -o ./publish
- uses: azure/login@v2
with:
client-id: $
tenant-id: $
subscription-id: $
- uses: azure/webapps-deploy@v3
with:
app-name: app-myapi-prod
slot-name: staging
package: ./publish
- name: Swap slots
run: |
az webapp deployment slot swap \
--name app-myapi-prod \
--resource-group rg-app-prod \
--slot staging \
--target-slot production
For AKS (GitHub Actions + ArgoCD):
In the Kubernetes ecosystem, we prefer GitOps with ArgoCD - the pipeline builds the image and pushes it to ACR, ArgoCD synchronises the manifest from the repository to the cluster. We covered the details in our article on Azure DevOps vs GitHub Actions.
Common Migration Mistakes
Based on migration projects we’ve delivered - here are the most common problems:
1. No Landing Zone - “we’ll put everything in one subscription”
Result: after 6 months nobody knows who’s paying for what, who has what permissions, and networking is a flat network without segmentation. Unwinding this state costs 2-3× more than doing it properly from the start.
2. Choosing an oversized SKU “just in case”
Teams accustomed to on-premise buy D8s_v5 (8 vCPU, 32 GB) for an application that needs 500 MB RAM. In the cloud, you start small and scale up. App Service P1v3 with autoscaling is a better choice than P3v3 running constantly.
3. Ignoring egress costs
Outbound traffic from Azure costs ~$0.087/GB (to the internet). If your application generates significant outbound traffic (e.g., integrations with external APIs, backup to external storage) - egress costs can surprise you. Private Endpoints eliminate this cost for communication between Azure services in the same region.
4. .NET Framework on Linux
App Service on Linux supports only .NET Core/.NET 5+. If the application is on .NET Framework 4.x - you need a Windows App Service Plan (more expensive) or Windows containers (slower cold start, fewer scaling options). Consider upgrading to .NET 8 before migration - it’s an investment that pays off.
5. No Private Endpoints
By default, Azure SQL Database, Storage Account, and other PaaS services are accessible from the public internet. A firewall rule with allowed IPs is an option, but it’s not Zero Trust. Private Endpoint places the service in your VNet with a private IP address - the only way to access it is being inside the virtual network.
6. Manual deployments after migration
“We’ll deploy manually for now, CI/CD later.” Later never comes. Deployment automation isn’t a luxury - it’s operational hygiene. Swap slots in App Service + GitHub Actions is a 2-hour configuration.
Timeline and Costs
| Phase | Duration | Consulting cost (typical) |
|---|---|---|
| Assessment + migration plan | 1-2 weeks | $2,000 - $4,000 |
| Landing Zone (Terraform/Bicep) | 1-2 weeks | $3,000 - $6,000 |
| Database migration | 1-3 weeks | $2,500 - $5,000 |
| Application deployment + CI/CD | 1-2 weeks | $2,500 - $4,500 |
| Testing + cutover + stabilisation | 1-2 weeks | $2,000 - $3,000 |
| TOTAL (App Service) | 5-10 weeks | $12,000 - $22,500 |
| TOTAL (AKS) | 8-14 weeks | $17,500 - $35,000 |
What affects cost:
- Number of applications to migrate (each additional +20-40% time)
- State of source code (.NET Framework vs .NET 8)
- Integration complexity (how many external systems?)
- Compliance requirements (additional documentation and configuration)
- Whether a Landing Zone already exists or needs to be built from scratch
Funding: The Microsoft Azure Migrate and Modernize programme offers Azure credits for the first 6 months after migration. Value depends on workload size - typically $10,000-$50,000 in credits.
How We Can Help
At Devopsity, we specialise in Azure migrations for organisations running .NET workloads. Typical engagement:
- Assessment (1-2 days) - analysis of current infrastructure, App Service vs AKS recommendation, preliminary Azure cost estimate
- Landing Zone (1-2 weeks) - Terraform setup, networking, governance, RBAC
- Migration (2-6 weeks) - database, application, CI/CD, cutover
- Optimisation (ongoing) - right-sizing, Reserved Instances, cost monitoring
If you’re planning a .NET migration to Azure - let’s discuss your case. No commitment, no sales pitch.
Planning an Azure migration?
Book a free 30-minute technical consultation. We'll discuss your infrastructure and the best migration path.
Frequently Asked Questions
Can I migrate a .NET Framework 4.x application to Azure without rewriting it to .NET 8?
Yes. Windows App Service supports .NET Framework 4.x natively. Alternatively - Windows containers on AKS. But long-term, plan an upgrade to .NET 8: better performance, Linux support (cheaper plans), active framework development.
How long does migrating a single .NET application to Azure take?
Minimum 4-5 weeks (simple application + SQL on App Service). Typically 6-10 weeks for medium complexity. Migration to AKS with full Landing Zone - 10-14 weeks. The biggest impact on timeline is database state and number of integrations.
How much does Azure cost monthly after migration?
For a typical .NET application (API + frontend + SQL + cache): $400-$800/month on App Service, $600-$1200/month on AKS. With Azure Hybrid Benefit (existing Windows/SQL licences) - 30-55% cheaper.
Do I need Azure Firewall?
Not always. For small deployments (1-2 applications), NSG + UDR is sufficient. Add Azure Firewall when you have requirements for centralised egress traffic inspection, logging all network traffic, or compliance requires an L7 firewall.
What about data residency in the EU?
Azure offers multiple European regions (West Europe in the Netherlands, North Europe in Ireland, plus country-specific regions like France Central, Germany West Central, and others). For GDPR compliance, keeping data within EU regions is straightforward. Azure Compliance Manager provides documentation for regulatory requirements.