Azure App Service Guide for .NET Developers
Introduction
Azure App Service is Microsoftβs Platform-as-a-Service (PaaS) offering for hosting web applications, REST APIs, and mobile backends. This comprehensive guide covers everything from basic deployment to advanced features like deployment slots, autoscaling, and networking.
Table of Contents
- App Service Plans
- Deployment Options
- Deployment Slots and Blue-Green Deployments
- Configuration Management
- Custom Domains and SSL/TLS
- Authentication and Authorization
- Scaling Strategies
- Networking
- Logging and Diagnostics
- Performance Optimization
- Container Deployments
- Cost Optimization
- Interview Questions
App Service Plans
An App Service plan defines the compute resources for your web app. Understanding tiers is crucial for performance and cost optimization.
Service Tiers Comparison
| Tier | Use Case | Features | Scaling | Price Range |
|---|---|---|---|---|
| Free (F1) | Development/testing | 1 GB disk, 60 CPU min/day | None | Free |
| Shared (D1) | Dev/test, low traffic | Custom domains | None | ~$10/month |
| Basic (B1-B3) | Light production | Manual scaling, SSL | Manual (3 instances) | ~$50-200/month |
| Standard (S1-S3) | Production | Autoscale, slots, backups | Auto (10 instances) | ~$70-280/month |
| Premium (P1v3-P3v3) | High-performance | VNet, more slots, higher limits | Auto (30 instances) | ~$140-560/month |
| Isolated (I1v2-I3v2) | Enterprise/compliance | Dedicated network, highest scale | Auto (100 instances) | ~$300-1200/month |
Choosing the Right Plan
What are your requirements?
βββ Development/testing only?
β βββ Free or Basic tier
βββ Production with consistent traffic?
β βββ Standard tier
βββ High traffic or need VNet integration?
β βββ Premium tier
βββ Compliance requirements (HIPAA, PCI)?
β βββ Isolated tier (App Service Environment)
βββ Container-based deployment?
βββ Premium or Isolated tier (Linux)
Creating an App Service Plan
# Azure CLI
az appservice plan create \
--name my-app-plan \
--resource-group my-rg \
--sku P1v3 \
--is-linux \
--location eastus
# Create web app in the plan
az webapp create \
--name my-web-app \
--resource-group my-rg \
--plan my-app-plan \
--runtime "DOTNET|8.0"
// Using Azure SDK for .NET
public async Task CreateAppServiceAsync()
{
var armClient = new ArmClient(new DefaultAzureCredential());
var subscription = await armClient.GetDefaultSubscriptionAsync();
var resourceGroup = await subscription
.GetResourceGroups()
.GetAsync("my-rg");
// Create App Service Plan
var planData = new AppServicePlanData(AzureLocation.EastUS)
{
Sku = new AppServiceSkuDescription
{
Name = "P1v3",
Tier = "PremiumV3",
Capacity = 1
},
Kind = "linux",
IsReserved = true // Required for Linux
};
var plan = await resourceGroup.Value
.GetAppServicePlans()
.CreateOrUpdateAsync(WaitUntil.Completed, "my-app-plan", planData);
// Create Web App
var webAppData = new WebSiteData(AzureLocation.EastUS)
{
AppServicePlanId = plan.Value.Id,
SiteConfig = new SiteConfigProperties
{
LinuxFxVersion = "DOTNETCORE|8.0",
AlwaysOn = true
}
};
await resourceGroup.Value
.GetWebSites()
.CreateOrUpdateAsync(WaitUntil.Completed, "my-web-app", webAppData);
}
Deployment Options
1. ZIP Deploy (Recommended for CI/CD)
# Build and publish
dotnet publish -c Release -o ./publish
# ZIP the output
cd publish && zip -r ../app.zip .
# Deploy using Azure CLI
az webapp deployment source config-zip \
--resource-group my-rg \
--name my-web-app \
--src app.zip
2. Git Deploy
# Configure local git deployment
az webapp deployment source config-local-git \
--name my-web-app \
--resource-group my-rg
# Get deployment URL
az webapp deployment list-publishing-credentials \
--name my-web-app \
--resource-group my-rg \
--query scmUri
# Add Azure as remote and push
git remote add azure https://<user>@my-web-app.scm.azurewebsites.net/my-web-app.git
git push azure main
3. GitHub Actions
# .github/workflows/azure-webapp.yml
name: Deploy to Azure App Service
on:
push:
branches: [main]
env:
AZURE_WEBAPP_NAME: my-web-app
DOTNET_VERSION: '8.0.x'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Build and Publish
run: |
dotnet restore
dotnet build --configuration Release
dotnet publish -c Release -o ./publish
- name: Deploy to Azure
uses: azure/webapps-deploy@v3
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ./publish
4. Azure DevOps Pipeline
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
azureSubscription: 'MyAzureSubscription'
webAppName: 'my-web-app'
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- task: UseDotNet@2
inputs:
version: '8.0.x'
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
- stage: Deploy
dependsOn: Build
jobs:
- deployment: DeployJob
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: '$(azureSubscription)'
appName: '$(webAppName)'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
Deployment Slots and Blue-Green Deployments
Deployment slots allow you to run multiple versions of your app simultaneously. This enables zero-downtime deployments and easy rollbacks.
Understanding Slots
App Service: my-web-app
βββ Production Slot (my-web-app.azurewebsites.net)
β βββ Currently serving users
βββ Staging Slot (my-web-app-staging.azurewebsites.net)
β βββ Deploy new version here first
βββ Testing Slot (my-web-app-testing.azurewebsites.net)
βββ For QA testing
Creating Deployment Slots
# Create staging slot
az webapp deployment slot create \
--name my-web-app \
--resource-group my-rg \
--slot staging \
--configuration-source my-web-app
# Deploy to staging
az webapp deployment source config-zip \
--name my-web-app \
--resource-group my-rg \
--slot staging \
--src app.zip
Blue-Green Deployment Strategy
Step 1: Deploy to staging (green)
Production (blue) β Serving traffic
Staging (green) β New version deployed
Step 2: Test staging slot
Access: my-web-app-staging.azurewebsites.net
Run smoke tests, health checks
Step 3: Swap slots
Production (green) β Now serving new version
Staging (blue) β Contains old version (rollback target)
Step 4: If issues found, swap back immediately
Implementing Blue-Green Deployment
# 1. Deploy to staging slot
az webapp deployment source config-zip \
--name my-web-app \
--resource-group my-rg \
--slot staging \
--src app.zip
# 2. Warm up staging (optional but recommended)
curl -I https://my-web-app-staging.azurewebsites.net/health
# 3. Run smoke tests against staging
./run-smoke-tests.sh https://my-web-app-staging.azurewebsites.net
# 4. Swap staging to production (zero-downtime)
az webapp deployment slot swap \
--name my-web-app \
--resource-group my-rg \
--slot staging \
--target-slot production
# 5. If issues found, swap back immediately
az webapp deployment slot swap \
--name my-web-app \
--resource-group my-rg \
--slot production \
--target-slot staging
Slot-Specific Settings (Sticky Settings)
# Mark settings as slot-specific (won't swap)
az webapp config appsettings set \
--name my-web-app \
--resource-group my-rg \
--slot-settings "SLOT_NAME=production" "ENVIRONMENT=prod"
# For staging slot
az webapp config appsettings set \
--name my-web-app \
--resource-group my-rg \
--slot staging \
--slot-settings "SLOT_NAME=staging" "ENVIRONMENT=staging"
What Gets Swapped vs. What Doesnβt
| Swapped (Default) | Not Swapped (Sticky) |
|---|---|
| General settings (framework version, etc.) | Publishing endpoints |
| App settings (unless marked sticky) | Custom domain names |
| Connection strings (unless marked sticky) | Certificates and TLS/SSL settings |
| Handler mappings | Scale settings |
| Public certificates | WebJobs schedulers |
| Virtual applications and directories | IP restrictions |
| Managed identities | Hybrid connections |
| Diagnostic settings |
Configuration Management
Application Settings
// Configuration in ASP.NET Core
var builder = WebApplication.CreateBuilder(args);
// Azure App Service settings automatically loaded
// They appear as environment variables
// Access configuration
var connectionString = builder.Configuration.GetConnectionString("Database");
var apiKey = builder.Configuration["ExternalApi:ApiKey"];
// Strongly-typed configuration
builder.Services.Configure<ExternalApiOptions>(
builder.Configuration.GetSection("ExternalApi"));
# Set app settings via CLI
az webapp config appsettings set \
--name my-web-app \
--resource-group my-rg \
--settings \
"ConnectionStrings__Database=Server=..." \
"ExternalApi__ApiKey=secret" \
"ASPNETCORE_ENVIRONMENT=Production"
Connection Strings
# Set connection strings (separate from app settings)
az webapp config connection-string set \
--name my-web-app \
--resource-group my-rg \
--connection-string-type SQLAzure \
--settings Database="Server=tcp:myserver.database.windows.net;..."
Key Vault Integration
// Program.cs - Load secrets from Key Vault
var builder = WebApplication.CreateBuilder(args);
// Add Key Vault configuration provider
if (builder.Environment.IsProduction())
{
var keyVaultEndpoint = builder.Configuration["KeyVaultEndpoint"];
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultEndpoint),
new DefaultAzureCredential());
}
// Secrets are now available like regular configuration
var secret = builder.Configuration["MySecret"];
# Enable managed identity
az webapp identity assign \
--name my-web-app \
--resource-group my-rg
# Grant Key Vault access
az keyvault set-policy \
--name my-keyvault \
--object-id <managed-identity-principal-id> \
--secret-permissions get list
# Reference Key Vault secrets in app settings
az webapp config appsettings set \
--name my-web-app \
--resource-group my-rg \
--settings "MySecret=@Microsoft.KeyVault(SecretUri=https://my-keyvault.vault.azure.net/secrets/MySecret/)"
Custom Domains and SSL/TLS
Adding Custom Domain
# 1. Add CNAME/A record in DNS
# CNAME: www.example.com β my-web-app.azurewebsites.net
# A record: example.com β <App Service IP>
# TXT record: asuid.www.example.com β <Custom Domain Verification ID>
# 2. Add custom domain to App Service
az webapp config hostname add \
--webapp-name my-web-app \
--resource-group my-rg \
--hostname www.example.com
SSL/TLS Certificates
# Option 1: Free managed certificate (auto-renewed)
az webapp config ssl create \
--name my-web-app \
--resource-group my-rg \
--hostname www.example.com
# Option 2: Import certificate from Key Vault
az webapp config ssl import \
--name my-web-app \
--resource-group my-rg \
--key-vault my-keyvault \
--key-vault-certificate-name my-cert
# Option 3: Upload PFX certificate
az webapp config ssl upload \
--name my-web-app \
--resource-group my-rg \
--certificate-file mycert.pfx \
--certificate-password "password"
# Bind certificate to hostname
az webapp config ssl bind \
--name my-web-app \
--resource-group my-rg \
--certificate-thumbprint <thumbprint> \
--ssl-type SNI
Enforcing HTTPS
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Force HTTPS redirect
if (app.Environment.IsProduction())
{
app.UseHttpsRedirection();
app.UseHsts();
}
# Enforce HTTPS at platform level
az webapp update \
--name my-web-app \
--resource-group my-rg \
--https-only true
# Set minimum TLS version
az webapp config set \
--name my-web-app \
--resource-group my-rg \
--min-tls-version 1.2
Authentication and Authorization
Azure App Service provides built-in authentication (βEasy Authβ) without code changes.
Enable Easy Auth
# Enable authentication with Entra ID
az webapp auth update \
--name my-web-app \
--resource-group my-rg \
--enabled true \
--action LoginWithAzureActiveDirectory \
--aad-client-id <app-registration-client-id> \
--aad-client-secret <client-secret> \
--aad-token-issuer-url https://login.microsoftonline.com/<tenant-id>/v2.0
Accessing User Claims in Code
// Easy Auth adds headers with user info
public class UserController : Controller
{
[HttpGet("me")]
public IActionResult GetCurrentUser()
{
// Easy Auth headers
var userId = Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"].FirstOrDefault();
var userName = Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"].FirstOrDefault();
// Or parse the full principal
var principalHeader = Request.Headers["X-MS-CLIENT-PRINCIPAL"].FirstOrDefault();
if (!string.IsNullOrEmpty(principalHeader))
{
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(principalHeader));
var principal = JsonSerializer.Deserialize<ClientPrincipal>(decoded);
}
return Ok(new { userId, userName });
}
}
public class ClientPrincipal
{
public string IdentityProvider { get; set; }
public string UserId { get; set; }
public string UserDetails { get; set; }
public IEnumerable<UserClaim> UserClaims { get; set; }
}
public class UserClaim
{
public string Type { get; set; }
public string Value { get; set; }
}
Scaling Strategies
Manual Scaling
# Scale up (vertical) - more powerful instance
az appservice plan update \
--name my-app-plan \
--resource-group my-rg \
--sku P2v3
# Scale out (horizontal) - more instances
az appservice plan update \
--name my-app-plan \
--resource-group my-rg \
--number-of-workers 3
Autoscale Configuration
# Enable autoscale based on CPU
az monitor autoscale create \
--resource-group my-rg \
--resource my-app-plan \
--resource-type Microsoft.Web/serverfarms \
--name my-autoscale-setting \
--min-count 2 \
--max-count 10 \
--count 2
# Add scale-out rule (CPU > 70%)
az monitor autoscale rule create \
--resource-group my-rg \
--autoscale-name my-autoscale-setting \
--condition "CpuPercentage > 70 avg 5m" \
--scale out 2
# Add scale-in rule (CPU < 30%)
az monitor autoscale rule create \
--resource-group my-rg \
--autoscale-name my-autoscale-setting \
--condition "CpuPercentage < 30 avg 5m" \
--scale in 1 \
--cooldown 10
Autoscale Best Practices
// 1. Disable ARR Affinity for better load distribution
// (Session affinity prevents effective scaling)
az webapp config set \
--name my-web-app \
--resource-group my-rg \
--generic-configurations '{"clientAffinityEnabled": false}'
// 2. Always use scale-out AND scale-in rules together
// Otherwise you'll hit max/min limits and get stuck
// 3. Use appropriate cool-down periods
// Minimum 5-10 minutes to let metrics stabilize
// 4. Set appropriate thresholds with margin
// Scale out at 70%, scale in at 30% (not 70% for both!)
// 5. Consider using multiple metrics
// CPU + Memory + Request Count for more accurate scaling
Automatic Scaling (Premium Plans)
# Enable automatic scaling (simpler than autoscale)
az webapp update \
--name my-web-app \
--resource-group my-rg \
--set siteConfig.autoHealEnabled=true
# Set maximum burst (max instances)
az resource update \
--resource-group my-rg \
--name my-web-app \
--resource-type "Microsoft.Web/sites" \
--set properties.siteConfig.functionAppScaleLimit=10
Networking
VNet Integration
# Create VNet and subnet
az network vnet create \
--name my-vnet \
--resource-group my-rg \
--address-prefix 10.0.0.0/16 \
--subnet-name web-subnet \
--subnet-prefix 10.0.1.0/24
# Delegate subnet to App Service
az network vnet subnet update \
--name web-subnet \
--vnet-name my-vnet \
--resource-group my-rg \
--delegations Microsoft.Web/serverFarms
# Enable VNet integration
az webapp vnet-integration add \
--name my-web-app \
--resource-group my-rg \
--vnet my-vnet \
--subnet web-subnet
Private Endpoints
# Create private endpoint for App Service
az network private-endpoint create \
--name my-private-endpoint \
--resource-group my-rg \
--vnet-name my-vnet \
--subnet private-endpoints-subnet \
--private-connection-resource-id <app-service-resource-id> \
--group-ids sites \
--connection-name my-private-connection
# Create private DNS zone
az network private-dns zone create \
--name privatelink.azurewebsites.net \
--resource-group my-rg
# Link DNS zone to VNet
az network private-dns link vnet create \
--name my-dns-link \
--resource-group my-rg \
--zone-name privatelink.azurewebsites.net \
--virtual-network my-vnet \
--registration-enabled false
Access Restrictions (IP Filtering)
# Allow specific IP range
az webapp config access-restriction add \
--name my-web-app \
--resource-group my-rg \
--rule-name "AllowOffice" \
--action Allow \
--ip-address 203.0.113.0/24 \
--priority 100
# Allow from specific VNet
az webapp config access-restriction add \
--name my-web-app \
--resource-group my-rg \
--rule-name "AllowVNet" \
--action Allow \
--vnet-name my-vnet \
--subnet web-subnet \
--priority 200
# Deny all other traffic (implicit)
az webapp config access-restriction set \
--name my-web-app \
--resource-group my-rg \
--default-action Deny
Logging and Diagnostics
Enable Logging
# Enable application logging
az webapp log config \
--name my-web-app \
--resource-group my-rg \
--application-logging filesystem \
--level verbose \
--detailed-error-messages true \
--failed-request-tracing true
# Stream logs in real-time
az webapp log tail \
--name my-web-app \
--resource-group my-rg
Application Insights Integration
// Program.cs
builder.Services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
});
// Custom telemetry
public class OrderService
{
private readonly TelemetryClient _telemetry;
public async Task ProcessOrderAsync(Order order)
{
using var operation = _telemetry.StartOperation<RequestTelemetry>("ProcessOrder");
_telemetry.TrackEvent("OrderReceived", new Dictionary<string, string>
{
["OrderId"] = order.Id.ToString(),
["CustomerId"] = order.CustomerId
});
try
{
await DoProcessingAsync(order);
operation.Telemetry.Success = true;
}
catch (Exception ex)
{
_telemetry.TrackException(ex);
operation.Telemetry.Success = false;
throw;
}
}
}
# Enable Application Insights
az webapp config appsettings set \
--name my-web-app \
--resource-group my-rg \
--settings "APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=..."
Health Checks
// Program.cs
builder.Services.AddHealthChecks()
.AddSqlServer(
connectionString: builder.Configuration.GetConnectionString("Database"),
name: "database")
.AddRedis(
redisConnectionString: builder.Configuration.GetConnectionString("Redis"),
name: "redis")
.AddUrlGroup(
new Uri("https://external-api.com/health"),
name: "external-api");
var app = builder.Build();
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
# Configure health check in App Service
az webapp config set \
--name my-web-app \
--resource-group my-rg \
--generic-configurations '{"healthCheckPath": "/health"}'
Performance Optimization
Always On
# Enable Always On (prevents cold starts)
az webapp config set \
--name my-web-app \
--resource-group my-rg \
--always-on true
Local Cache
# Enable local cache for faster file access
az webapp config appsettings set \
--name my-web-app \
--resource-group my-rg \
--settings "WEBSITE_LOCAL_CACHE_OPTION=Always" \
"WEBSITE_LOCAL_CACHE_SIZEINMB=1000"
Compression
// Program.cs - Enable response compression
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
var app = builder.Build();
app.UseResponseCompression();
Caching Headers
// Set cache headers for static content
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append(
"Cache-Control", "public,max-age=31536000");
}
});
Container Deployments
Deploy Docker Container
# Using Azure Container Registry
az webapp create \
--name my-container-app \
--resource-group my-rg \
--plan my-app-plan \
--deployment-container-image-name myregistry.azurecr.io/myapp:latest
# Configure registry credentials
az webapp config container set \
--name my-container-app \
--resource-group my-rg \
--docker-registry-server-url https://myregistry.azurecr.io \
--docker-registry-server-user myregistry \
--docker-registry-server-password <password>
# Or use managed identity (recommended)
az webapp config set \
--name my-container-app \
--resource-group my-rg \
--generic-configurations '{"acrUseManagedIdentityCreds": true}'
Multi-Container (Docker Compose)
# docker-compose.yml
version: '3'
services:
web:
image: myregistry.azurecr.io/web:latest
ports:
- "80:80"
environment:
- REDIS_HOST=redis
redis:
image: redis:alpine
ports:
- "6379:6379"
# Deploy multi-container app
az webapp config container set \
--name my-multi-app \
--resource-group my-rg \
--multicontainer-config-type compose \
--multicontainer-config-file docker-compose.yml
Cost Optimization
Cost-Saving Strategies
| Strategy | Savings | When to Use |
|---|---|---|
| Dev/Test pricing | ~50% | Non-production environments |
| Reserved Instances | ~40% | Predictable production workloads |
| Autoscale | Variable | Variable traffic patterns |
| Stop dev environments | 100% when stopped | After-hours, weekends |
| Right-size plans | Variable | Over-provisioned resources |
Auto-Shutdown for Dev Environments
# Using Azure Automation or Logic Apps
# Schedule: Stop at 7 PM, Start at 7 AM
# Stop command
az webapp stop --name dev-app --resource-group dev-rg
# Start command
az webapp start --name dev-app --resource-group dev-rg
Shared Plans for Dev/Test
# Multiple dev apps can share one plan
az webapp create --name dev-app-1 --plan shared-dev-plan --resource-group dev-rg
az webapp create --name dev-app-2 --plan shared-dev-plan --resource-group dev-rg
az webapp create --name dev-app-3 --plan shared-dev-plan --resource-group dev-rg
Interview Questions
1. What is the difference between scaling up and scaling out?
Answer:
- Scaling up (vertical): Moving to a more powerful instance (e.g., S1 β S3). Increases CPU, memory per instance. Simple but has limits.
- Scaling out (horizontal): Adding more instances. Better for handling concurrent requests. Requires stateless app design. Preferred for production workloads.
Best practice: Design for scale-out (stateless apps), then scale up if individual requests need more resources.
2. How do deployment slots enable zero-downtime deployments?
Answer: Deployment slots provide:
- Separate environment for testing new code
- Warm-up before traffic routing
- Instant traffic switch (slot swap)
- Instant rollback capability
The swap operation:
- Applies source slotβs settings to target slot instances
- Waits for instances to restart with new settings
- Swaps virtual IPs (traffic routing)
- No requests are dropped during the swap
3. When would you use VNet Integration vs Private Endpoints?
Answer:
-
VNet Integration: Allows App Service to access resources inside a VNet (outbound). Use when your app needs to connect to VMs, databases, or services in a private network.
-
Private Endpoints: Makes App Service accessible only via private IP (inbound). Use when you want to restrict access to your app from the internet.
Typical architecture: Both together - VNet integration for app to access backend resources, private endpoint to restrict who can access the app.
4. What happens during an App Service slot swap?
Answer:
- Source slot settings (connection strings, app settings) applied to target slot workers
- Target slot workers restart with new configuration
- Warm-up requests sent to target slot (using the source slotβs code)
- Once warm-up completes, virtual IPs are swapped
- Source slot now has targetβs previous code (rollback point)
Key points:
- Settings marked βslot-specificβ donβt swap
- Swap can be configured with traffic routing for gradual rollout
- Auto-swap can be enabled for staging slots
5. How do you troubleshoot a slow App Service application?
Answer:
- Check Application Insights: Look at request duration, dependencies, exceptions
- Review metrics: CPU, memory, HTTP queue length, response times
- Enable profiler: Application Insights profiler for code-level analysis
- Check dependencies: Database, external APIs, Redis - are they slow?
- Review scaling: Is the app scaled appropriately for load?
- Check for cold starts: Is Always On enabled?
- Network issues: Check VNet integration, private endpoints
- Code issues: Sync over async, N+1 queries, missing caching
Key Takeaways
- Choose the right tier - Basic for dev, Standard+ for production
- Use deployment slots - Zero-downtime deployments and easy rollbacks
- Enable autoscaling - Always set scale-in rules alongside scale-out
- Use managed identity - No credentials to manage
- Enable Application Insights - Essential for production monitoring
- Consider networking - VNet integration for backend access, private endpoints for secure access
- Enable Always On - Prevents cold starts in production
- Use slot-specific settings - Connection strings, environment variables per slot