Azure App Service Setup
Azure App Service provides a fully managed platform for hosting web applications, including static documentation sites generated by DocFX. This guide covers comprehensive setup for production-ready documentation hosting with scalability, security, and monitoring capabilities.
Prerequisites
Before setting up Azure App Service, ensure you have:
- Azure subscription with appropriate permissions
- Resource group created or planned
- Azure CLI installed and authenticated
- Domain name registered (for custom domains)
- SSL certificate or plan for managed certificates
Planning and Architecture
Resource Planning
Naming Conventions:
Resource Naming Pattern: {project}-{environment}-{service}-{region}
├── docs-dev-app-eastus # Development App Service
├── docs-prod-app-eastus # Production App Service
├── docs-dev-plan-eastus # Development App Service Plan
├── docs-prod-plan-eastus # Production App Service Plan
├── docs-insights-eastus # Application Insights
└── docs-shared-rg-eastus # Shared Resource Group
Environment Strategy:
Environment | Purpose | Configuration | Scaling |
---|---|---|---|
Development | Testing and staging | Basic tier, manual scaling | 1 instance |
Production | Live documentation | Standard/Premium tier, auto-scaling | 2-10 instances |
Preview | Pull request previews | Shared with development | On-demand |
Architecture Overview
graph TB
A[Azure DevOps Pipeline] --> B[Build Artifacts]
B --> C[Azure App Service]
C --> D[Custom Domain]
C --> E[Application Insights]
C --> F[Azure CDN]
G[Users] --> D
D --> H[SSL Termination]
H --> C
E --> I[Monitoring Dashboard]
F --> J[Global Content Delivery]
Resource Group Setup
Creating Resource Groups
Development Environment:
# Create development resource group
az group create \
--name "docs-dev-rg-eastus" \
--location "eastus" \
--tags "Environment=Development" "Project=Documentation" "Owner=DevOps"
Production Environment:
# Create production resource group
az group create \
--name "docs-prod-rg-eastus" \
--location "eastus" \
--tags "Environment=Production" "Project=Documentation" "Owner=DevOps"
Resource Group Configuration
Governance Settings:
{
"properties": {
"tags": {
"Environment": "Production",
"Project": "Documentation",
"Owner": "DevOps Team",
"CostCenter": "IT-Documentation",
"CreatedDate": "2025-07-06"
}
},
"location": "eastus"
}
App Service Plan Configuration
Plan Creation
Development Plan:
# Create development App Service Plan
az appservice plan create \
--name "docs-dev-plan-eastus" \
--resource-group "docs-dev-rg-eastus" \
--location "eastus" \
--sku "B1" \
--is-linux false \
--tags "Environment=Development"
Production Plan:
# Create production App Service Plan
az appservice plan create \
--name "docs-prod-plan-eastus" \
--resource-group "docs-prod-rg-eastus" \
--location "eastus" \
--sku "S1" \
--is-linux false \
--tags "Environment=Production"
Scaling Configuration
Auto-scaling Rules:
{
"profiles": [
{
"name": "Default",
"capacity": {
"minimum": "2",
"maximum": "10",
"default": "2"
},
"rules": [
{
"metricTrigger": {
"metricName": "CpuPercentage",
"metricResourceUri": "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/serverfarms/{plan-name}",
"timeGrain": "PT1M",
"statistic": "Average",
"timeWindow": "PT5M",
"timeAggregation": "Average",
"operator": "GreaterThan",
"threshold": 70
},
"scaleAction": {
"direction": "Increase",
"type": "ChangeCount",
"value": "1",
"cooldown": "PT5M"
}
},
{
"metricTrigger": {
"metricName": "CpuPercentage",
"metricResourceUri": "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/serverfarms/{plan-name}",
"timeGrain": "PT1M",
"statistic": "Average",
"timeWindow": "PT10M",
"timeAggregation": "Average",
"operator": "LessThan",
"threshold": 30
},
"scaleAction": {
"direction": "Decrease",
"type": "ChangeCount",
"value": "1",
"cooldown": "PT10M"
}
}
]
}
]
}
App Service Creation
Web App Setup
Development Web App:
# Create development web app
az webapp create \
--name "docs-dev-app-eastus" \
--resource-group "docs-dev-rg-eastus" \
--plan "docs-dev-plan-eastus" \
--runtime "node|18-lts" \
--tags "Environment=Development"
Production Web App:
# Create production web app
az webapp create \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--plan "docs-prod-plan-eastus" \
--runtime "node|18-lts" \
--tags "Environment=Production"
Application Configuration
Application Settings:
# Configure app settings for production
az webapp config appsettings set \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--settings \
"ENVIRONMENT=Production" \
"NODE_ENV=production" \
"WEBSITE_NODE_DEFAULT_VERSION=18" \
"WEBSITE_RUN_FROM_PACKAGE=1" \
"SCM_DO_BUILD_DURING_DEPLOYMENT=false"
Connection Strings (if needed):
# Add connection strings if required
az webapp config connection-string set \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--connection-string-type "Custom" \
--settings "ApplicationInsights=InstrumentationKey={key}"
Deployment Configuration
Deployment Source Setup:
# Configure deployment from Azure DevOps
az webapp deployment source config \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--repo-url "https://dev.azure.com/organization/project/_git/docs" \
--branch "main" \
--manual-integration
Deployment Slots (Production Only):
# Create staging slot for production
az webapp deployment slot create \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--slot "staging"
# Configure slot-specific settings
az webapp config appsettings set \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--slot "staging" \
--settings "ENVIRONMENT=Staging"
Custom Domain Configuration
Domain Setup
Add Custom Domain:
# Add custom domain to web app
az webapp config hostname add \
--webapp-name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--hostname "docs.yourdomain.com"
DNS Configuration:
# DNS Records Required
docs.yourdomain.com CNAME docs-prod-app-eastus.azurewebsites.net
asuid.docs.yourdomain.com TXT {verification-id}
SSL Certificate Management
Managed SSL Certificate:
# Create managed SSL certificate
az webapp config ssl create \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--hostname "docs.yourdomain.com"
# Bind SSL certificate
az webapp config ssl bind \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--certificate-thumbprint "{thumbprint}" \
--ssl-type "Managed"
Custom SSL Certificate:
# Upload custom certificate
az webapp config ssl upload \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--certificate-file "certificate.pfx" \
--certificate-password "{password}"
Security Configuration
Network Security
IP Restrictions:
{
"ipSecurityRestrictions": [
{
"ipAddress": "192.168.1.0/24",
"action": "Allow",
"priority": 100,
"name": "Corporate Network",
"description": "Allow corporate IP range"
},
{
"ipAddress": "0.0.0.0/0",
"action": "Deny",
"priority": 2147483647,
"name": "Deny All",
"description": "Deny all other traffic"
}
]
}
Authentication Configuration:
# Configure Azure AD authentication (optional)
az webapp auth update \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--enabled true \
--action "LoginWithAzureActiveDirectory" \
--aad-client-id "{client-id}" \
--aad-client-secret "{client-secret}" \
--aad-tenant-id "{tenant-id}"
Security Headers
Web.config for Security Headers:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Content-Type-Options" value="nosniff" />
<add name="X-Frame-Options" value="DENY" />
<add name="X-XSS-Protection" value="1; mode=block" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
<add name="Content-Security-Policy" value="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:" />
<add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
</customHeaders>
</httpProtocol>
<staticContent>
<mimeMap fileExtension=".json" mimeType="application/json" />
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
</staticContent>
<defaultDocument>
<files>
<clear />
<add value="index.html" />
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="HTTPS Redirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
<rule name="SPA Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Monitoring and Diagnostics
Application Insights Setup
Create Application Insights:
# Create Application Insights instance
az monitor app-insights component create \
--app "docs-insights-eastus" \
--location "eastus" \
--resource-group "docs-prod-rg-eastus" \
--application-type "web"
# Configure App Service to use Application Insights
az webapp config appsettings set \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--settings "APPINSIGHTS_INSTRUMENTATIONKEY={instrumentation-key}"
Custom Telemetry Configuration:
// Custom telemetry for documentation analytics
(function() {
var appInsights = window.appInsights || function(a) {
function b(a) { c[a] = function() { var b = arguments; c.queue.push(function() { c[a].apply(c, b) }) } }
var c = { config: a }, d = document, e = window;
setTimeout(function() {
var b = d.createElement("script");
b.src = a.url || "https://az416426.vo.msecnd.net/scripts/b/ai.2.min.js";
d.getElementsByTagName("script")[0].parentNode.appendChild(b)
});
try { c.cookie = d.cookie } catch (a) { }
c.queue = [];
for (var f = ["Event", "PageView", "Exception", "Trace", "DependencyData", "Metric", "PageViewPerformance"]; f.length;)
b("track" + f.pop());
if (b("startTrackPage"), b("stopTrackPage"), b("startTrackEvent"), b("stopTrackEvent"), b("addTelemetryInitializer"), b("setAuthenticatedUserContext"), b("clearAuthenticatedUserContext"), b("flush"), c.SeverityLevel = { Verbose: 0, Information: 1, Warning: 2, Error: 3, Critical: 4 }, !(!0 === a.disableExceptionTracking || a.extensionConfig && a.extensionConfig.ApplicationInsightsAnalytics && !0 === a.extensionConfig.ApplicationInsightsAnalytics.disableExceptionTracking)) {
b("_" + (f = "onerror"));
var g = e[f];
e[f] = function(a, b, d, e, h) { var i = g && g(a, b, d, e, h); return !0 !== i && c["_" + f]({ message: a, url: b, lineNumber: d, columnNumber: e, error: h }), i }
}
return c
}({
instrumentationKey: "{your-instrumentation-key}"
});
window.appInsights = appInsights, appInsights.queue && 0 === appInsights.queue.length && appInsights.trackPageView({});
// Custom documentation analytics
appInsights.addTelemetryInitializer(function(envelope) {
envelope.tags["ai.cloud.role"] = "Documentation Site";
envelope.data.baseData.properties = envelope.data.baseData.properties || {};
envelope.data.baseData.properties.documentationSection = getDocumentationSection();
envelope.data.baseData.properties.userAgent = navigator.userAgent;
});
function getDocumentationSection() {
var path = window.location.pathname;
if (path.includes('/api/')) return 'API Reference';
if (path.includes('/tutorials/')) return 'Tutorials';
if (path.includes('/articles/')) return 'Articles';
if (path.includes('/reference/')) return 'Reference';
return 'Other';
}
// Track search queries
document.addEventListener('DOMContentLoaded', function() {
var searchInput = document.querySelector('#search-input');
if (searchInput) {
searchInput.addEventListener('input', function(e) {
if (e.target.value.length > 3) {
appInsights.trackEvent('SearchQuery', {
query: e.target.value,
section: getDocumentationSection()
});
}
});
}
});
})();
Diagnostic Settings
Enable Diagnostic Logs:
# Enable application logs
az webapp log config \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--application-logging filesystem \
--level information \
--web-server-logging filesystem
# Configure log retention
az webapp log config \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--detailed-error-messages true \
--failed-request-tracing true
Performance Optimization
CDN Integration
Azure CDN Setup:
# Create CDN profile
az cdn profile create \
--name "docs-cdn-profile" \
--resource-group "docs-prod-rg-eastus" \
--sku "Standard_Microsoft"
# Create CDN endpoint
az cdn endpoint create \
--name "docs-cdn-endpoint" \
--profile-name "docs-cdn-profile" \
--resource-group "docs-prod-rg-eastus" \
--origin "docs-prod-app-eastus.azurewebsites.net" \
--origin-host-header "docs-prod-app-eastus.azurewebsites.net"
CDN Rules Configuration:
{
"deliveryRules": [
{
"name": "CacheStaticAssets",
"order": 1,
"conditions": [
{
"name": "UrlFileExtension",
"parameters": {
"extensions": ["css", "js", "png", "jpg", "jpeg", "gif", "svg", "ico", "woff", "woff2"],
"operator": "Equal",
"negateCondition": false,
"matchValues": []
}
}
],
"actions": [
{
"name": "CacheExpiration",
"parameters": {
"cacheBehavior": "Override",
"cacheType": "All",
"cacheDuration": "30.00:00:00"
}
}
]
},
{
"name": "CacheHTMLFiles",
"order": 2,
"conditions": [
{
"name": "UrlFileExtension",
"parameters": {
"extensions": ["html"],
"operator": "Equal",
"negateCondition": false,
"matchValues": []
}
}
],
"actions": [
{
"name": "CacheExpiration",
"parameters": {
"cacheBehavior": "Override",
"cacheType": "All",
"cacheDuration": "01:00:00"
}
}
]
}
]
}
Compression Configuration
Enable Compression:
# Enable dynamic compression
az webapp config set \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--use-32bit-worker-process false \
--web-sockets-enabled false \
--always-on true \
--http20-enabled true
Backup and Recovery
Backup Configuration
Automated Backups:
# Configure automated backups (Standard tier and above)
az webapp config backup create \
--resource-group "docs-prod-rg-eastus" \
--webapp-name "docs-prod-app-eastus" \
--backup-name "daily-backup" \
--storage-account-url "https://docsbackup.blob.core.windows.net/backups" \
--frequency 24 \
--retain-for-days 30
Disaster Recovery
Multi-Region Setup:
# Create secondary region deployment
az group create \
--name "docs-prod-rg-westus" \
--location "westus"
az appservice plan create \
--name "docs-prod-plan-westus" \
--resource-group "docs-prod-rg-westus" \
--location "westus" \
--sku "S1"
az webapp create \
--name "docs-prod-app-westus" \
--resource-group "docs-prod-rg-westus" \
--plan "docs-prod-plan-westus"
Validation and Testing
Health Checks
Application Health Monitoring:
# Configure health check endpoint
az webapp config set \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus" \
--health-check-path "/health"
Health Check Implementation:
<!-- health.html -->
<!DOCTYPE html>
<html>
<head>
<title>Health Check</title>
</head>
<body>
<h1>Service Health: OK</h1>
<script>
// Perform health checks
var healthData = {
status: "healthy",
timestamp: new Date().toISOString(),
checks: {
"site-load": window.location.href ? "pass" : "fail",
"search-available": document.querySelector("#search-input") ? "pass" : "fail"
}
};
console.log("Health check:", healthData);
</script>
</body>
</html>
Load Testing
Azure Load Testing:
# Create load test resource
az load create \
--name "docs-load-test" \
--resource-group "docs-prod-rg-eastus" \
--location "eastus"
Troubleshooting
Common Issues
Deployment Failures:
# Check deployment logs
az webapp log download \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus"
# Check application logs
az webapp log tail \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus"
Performance Issues:
# Check app service metrics
az monitor metrics list \
--resource "/subscriptions/{sub-id}/resourceGroups/docs-prod-rg-eastus/providers/Microsoft.Web/sites/docs-prod-app-eastus" \
--metric "CpuPercentage,MemoryPercentage,HttpResponseTime"
Diagnostic Commands
# App Service diagnostics
az webapp show \
--name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus"
# Check SSL certificate status
az webapp config ssl list \
--resource-group "docs-prod-rg-eastus"
# Validate custom domain
az webapp config hostname list \
--webapp-name "docs-prod-app-eastus" \
--resource-group "docs-prod-rg-eastus"
Next Steps
After setting up Azure App Service:
- Configure CI/CD Pipeline - Automate deployment from Azure DevOps
- Monitoring and Analytics - Set up comprehensive monitoring
- Performance Optimization - Optimize for speed and scale
- Security Hardening - Implement advanced security measures
Additional Resources
This Azure App Service setup provides enterprise-grade hosting for documentation sites with comprehensive security, monitoring, and performance optimization capabilities.