Skip to main content

Theoretical Foundation: Create an App Service


1. Initial Intuition​

Imagine you've developed a web application and need to put it on the internet. Without a managed service, you would need to provision a VM, install the operating system, configure the web server (IIS, Nginx, Apache), install the runtime for your language, configure SSL certificates, monitor server security updates, and much more, all before deploying your application.

Azure App Service eliminates this complexity. You focus exclusively on your code and application configurations. Microsoft manages the web server, runtime, security patches, platform availability, and physical infrastructure. You deploy the code and it simply works on a public URL.

An App Service is the object that represents your application within the App Service Plan: it's where you configure which runtime to use, what environment variables your application needs, authorization rules, custom domains, and where you deploy your code or container.


2. Context​

The Azure App Service ecosystem​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Why App Service exists​

App Service exists because most web applications share common infrastructure requirements: HTTP server, TLS/SSL, load balancing, health checks, logging, and monitoring. Managing this individually for each application is repetitive, error-prone, and expensive in engineering time.

App Service is a PaaS (Platform as a Service): you manage what runs (your code and configuration), Microsoft manages where and how it runs (infrastructure, patches, platform scalability).

What depends on App Service​

  • App Service Plan: mandatory infrastructure where the App Service is hosted
  • Deployment methods: code reaches the App Service via Git, FTP, Azure DevOps, GitHub Actions, ZIP deploy, etc.
  • Custom Domains and SSL: custom domains and certificates managed by App Service
  • VNet Integration: for access to resources in private networks
  • Managed Identity: for secure access to other Azure services without credentials

3. Concept Building​

3.1 What an App Service configures​

An App Service is an object with many configurable properties. The most important ones are:

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

3.2 App Settings and Connection Strings​

App Settings are environment variables injected into the application runtime. They are the correct way to pass configurations (external API URLs, feature flags, configuration parameters) to the app without hardcoding in the code.

In runtime, App Settings appear as environment variables:

  • In .NET: available via System.Environment.GetEnvironmentVariable("KEY")
  • In Node.js: via process.env.KEY
  • In Python: via os.environ.get("KEY")

Connection Strings are a special type of App Setting for database connection strings. They receive an automatic prefix depending on the type:

  • SQL Azure: SQLAZURECONNSTR_KeyName
  • SQL Server: SQLCONNSTR_KeyName
  • MySQL: MYSQLCONNSTR_KeyName
  • Custom: CUSTOMCONNSTR_KeyName

The practical difference: Connection Strings are exposed to the app exactly like App Settings in runtime. The separation is organizational and allows the portal to display and manage the two types separately.

3.3 Deployment Slots​

A Deployment Slot is a separate instance of the Web App, with its own URL, its own configurations, and its own deployed code, but running on the same App Service Plan.

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

The Slot Swap exchanges code between two slots instantly, without downtime. The staging warms up while the current code continues serving production. When the swap happens, requests are redirected to the slot that was in staging. If something goes wrong, you swap back in seconds.

Sticky (slot-specific) configurations: some configurations are not swapped during swap. They stay "stuck" to the slot. This is crucial so that the staging slot uses the staging database while production uses the production database, even after the swap. App Settings and Connection Strings can be marked as slot settings to remain fixed to the slot.

3.4 Authentication (Easy Auth)​

App Service has a built-in authentication module called Easy Auth that can be enabled without a single line of code in the app. It intercepts requests before they reach the app and verifies identity via:

  • Azure AD (Microsoft Entra ID)
  • Google
  • Facebook
  • GitHub
  • Twitter/X

When enabled, Easy Auth injects headers with authenticated user information that the app can consume.

3.5 Always On​

The Always On configuration keeps the app process running even without requests. Without it, App Service can shut down the process after a period of inactivity (~20 minutes), causing cold start on the next request.

Available only in Basic and above tiers. In the Free tier, Always On is not available and cold starts are frequent.

3.6 Diagnostic Logs​

App Service diagnostic logs have various types:

TypeWhat it capturesWhere to store
Application LoggingApplication log output (console.log, logging frameworks)File System (temporary) or Blob Storage
Web Server LoggingHTTP access logs (IIS logs)File System or Blob Storage
Detailed Error MessagesHTTP 4xx and 5xx error pagesFile System
Failed Request TracingFailed request traceFile System

File System Logging is temporary: automatically disables after 12 hours to prevent filling the app's storage. For persistent retention, use Blob Storage or Log Analytics via Diagnostic Settings.


4. Structural View​

Complete deploy and swap cycle​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Complete structure of an App Service​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

5. Practical Operation​

App Service lifecycle​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Critical non-obvious behaviors​

Changing an App Setting restarts the app. Whenever you save a change to App Settings or Connection Strings, App Service restarts the app process to apply the new environment variables. In production, this causes a brief moment of unavailability. Use deployment slots to change configurations without affecting production.

Portal App Settings override code variables. If your application has a DATABASE_URL variable configured in code but also defined as an App Setting in the portal, the portal App Setting wins. This can cause confusion when code configurations seem to have no effect.

The App Service name must be globally unique. The app's default URL is {name}.azurewebsites.net. Since this domain is shared globally, the name needs to be unique worldwide. "ecommerce" will already be taken, but "ecommerce-mycompany-prod" probably won't be.

Container-based apps require restart to pull new image. If you use a custom container with latest tag, App Service won't automatically pull a new image when the container is updated in the registry. You need to restart the app or configure CI/CD to trigger the restart. For production environments, avoid the latest tag; use versioned tags.

SCM (Kudu) is a powerful diagnostic tool. Each App Service has a Kudu site accessible at {name}.scm.azurewebsites.net. It offers: SSH console in the container, file system browser, real-time log viewing, and diagnostic tools. Requires Azure AD authentication.

Web sockets and HTTP/2 need to be explicitly enabled. By default, App Service disables Web Sockets and uses HTTP/1.1. For real-time applications (chat, live notifications) or apps that benefit from HTTP/2 (multiplexing), these features must be enabled in the app settings.


6. Implementation Methods​

Azure Portal​

When to use: initial creation, configuration exploration, one-off operations

Create App Service via portal:

  1. Portal > App Services > + Create > Web App
  2. Subscription and Resource Group
  3. Name: globally unique name
  4. Publish: Code or Container Image
  5. Runtime stack: select language and version
  6. Operating System: Linux or Windows (must match the Plan)
  7. Region: same region as desired Plan
  8. App Service Plan: select existing or create new
  9. Deployment tab: configure GitHub Actions if immediate CI/CD desired
  10. Networking tab: configure VNet Integration if needed
  11. Monitoring tab: enable Application Insights
  12. Review + Create

Azure CLI​

# Create basic Web App in existing Plan
az webapp create \
--resource-group "rg-webapp" \
--plan "asp-ecommerce-prod" \
--name "ecommerce-frontend" \
--runtime "NODE:20-lts" \
--deployment-local-git

# Create Linux Web App with Python
az webapp create \
--resource-group "rg-webapp" \
--plan "asp-api-prod" \
--name "ecommerce-api" \
--runtime "PYTHON:3.12"

# Create Windows Web App with .NET
az webapp create \
--resource-group "rg-webapp" \
--plan "asp-dotnet-prod" \
--name "ecommerce-admin" \
--runtime "DOTNET:8"

# Create Web App with custom container (Linux)
az webapp create \
--resource-group "rg-webapp" \
--plan "asp-containers-prod" \
--name "ecommerce-worker" \
--deployment-container-image-name "myregistry.azurecr.io/worker:v1.5"

# List available runtimes
az webapp list-runtimes --os-type linux --output table
az webapp list-runtimes --os-type windows --output table

# Configure App Settings
az webapp config appsettings set \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--settings \
NODE_ENV=production \
API_URL=https://api.ecommerce.com.br \
FEATURE_FLAG_CHECKOUT=true

# View current App Settings
az webapp config appsettings list \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--output table

# Delete an App Setting
az webapp config appsettings delete \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--setting-names "FEATURE_FLAG_CHECKOUT"

# Configure Connection Strings
az webapp config connection-string set \
--resource-group "rg-webapp" \
--name "ecommerce-api" \
--connection-string-type SQLAzure \
--settings \
DefaultConnection="Server=sql-ecommerce.database.windows.net;Database=ecomdb;..."

# Create Deployment Slot
az webapp deployment slot create \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--slot "staging" \
--configuration-source "ecommerce-frontend"

# List slots
az webapp deployment slot list \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--output table

# Perform slot swap (staging -> production)
az webapp deployment slot swap \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--slot "staging" \
--target-slot "production"

# Mark App Setting as slot-specific (doesn't swap)
az webapp config appsettings set \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--slot "staging" \
--slot-settings \
DATABASE_URL="mongodb://mongodb-staging.cosmos.azure.com"

# Enable Always On
az webapp config set \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--always-on true

# Configure HTTPS Only (force HTTP -> HTTPS redirect)
az webapp update \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--https-only true

# Enable System-Assigned Managed Identity
az webapp identity assign \
--resource-group "rg-webapp" \
--name "ecommerce-frontend"

# View Managed Identity Object ID (for RBAC)
az webapp identity show \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "principalId" \
--output tsv

# Configure VNet Integration
az webapp vnet-integration add \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--vnet "vnet-app" \
--subnet "subnet-webapp"

# Enable diagnostic logs for File System
az webapp log config \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--application-logging filesystem \
--level information \
--web-server-logging filesystem \
--detailed-error-messages true

# View logs in real-time (log streaming)
az webapp log tail \
--resource-group "rg-webapp" \
--name "ecommerce-frontend"

# Deploy ZIP file
az webapp deploy \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--src-path "./dist/app.zip" \
--type zip

# Restart the app
az webapp restart \
--resource-group "rg-webapp" \
--name "ecommerce-frontend"

# Stop the app
az webapp stop \
--resource-group "rg-webapp" \
--name "ecommerce-frontend"

# Start the app
az webapp start \
--resource-group "rg-webapp" \
--name "ecommerce-frontend"

Azure PowerShell​

# Create Web App
New-AzWebApp `
-ResourceGroupName "rg-webapp" `
-Name "ecommerce-frontend" `
-AppServicePlan "asp-ecommerce-prod" `
-Location "brazilsouth"

# Configure Linux runtime (after creation)
$app = Get-AzWebApp -ResourceGroupName "rg-webapp" -Name "ecommerce-frontend"
$app.SiteConfig.LinuxFxVersion = "NODE|20-lts"
$app.SiteConfig.AlwaysOn = $true
Set-AzWebApp -WebApp $app

# Configure App Settings
$appSettings = @{
"NODE_ENV" = "production"
"API_URL" = "https://api.ecommerce.com.br"
"FEATURE_NEW" = "true"
}
Set-AzWebApp `
-ResourceGroupName "rg-webapp" `
-Name "ecommerce-frontend" `
-AppSettings $appSettings

# Create slot
New-AzWebAppSlot `
-ResourceGroupName "rg-webapp" `
-Name "ecommerce-frontend" `
-Slot "staging"

# Slot swap
Switch-AzWebAppSlot `
-ResourceGroupName "rg-webapp" `
-Name "ecommerce-frontend" `
-SourceSlotName "staging" `
-DestinationSlotName "production"

# Enable Managed Identity
Set-AzWebApp `
-ResourceGroupName "rg-webapp" `
-Name "ecommerce-frontend" `
-AssignIdentity $true

# View current app state
Get-AzWebApp -ResourceGroupName "rg-webapp" -Name "ecommerce-frontend" |
Select-Object Name, State, DefaultHostName, `
@{N="Runtime"; E={$_.SiteConfig.LinuxFxVersion}}, `
@{N="AlwaysOn"; E={$_.SiteConfig.AlwaysOn}}

Bicep​

// Web App with complete configurations
resource webApp 'Microsoft.Web/sites@2022-09-01' = {
name: 'ecommerce-frontend'
location: 'brazilsouth'
identity: {
type: 'SystemAssigned' // Enable Managed Identity
}
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true // Force HTTPS
siteConfig: {
linuxFxVersion: 'NODE|20-lts' // Linux runtime
alwaysOn: true
minTlsVersion: '1.2'
ftpsState: 'Disabled' // Disable FTP (security)
http20Enabled: true // Enable HTTP/2
appSettings: [
{
name: 'NODE_ENV'
value: 'production'
}
{
name: 'API_URL'
value: 'https://api.ecommerce.com.br'
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsights.properties.ConnectionString
}
]
connectionStrings: [
{
name: 'DefaultConnection'
connectionString: '@Microsoft.KeyVault(SecretUri=${kvSecret.properties.secretUri})'
type: 'SQLAzure'
}
]
}
}
}

// Deployment Slot for staging
resource stagingSlot 'Microsoft.Web/sites/slots@2022-09-01' = {
parent: webApp
name: 'staging'
location: 'brazilsouth'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
siteConfig: {
linuxFxVersion: 'NODE|20-lts'
alwaysOn: true
appSettings: [
{
name: 'NODE_ENV'
value: 'staging'
}
]
}
}
}

7. Control and Security​

Essential security configurations​

HTTPS Only: automatically redirects HTTP requests to HTTPS. Should be enabled in any production app.

Minimum TLS Version: defines the minimum TLS version accepted. The current standard is TLS 1.2; avoid TLS 1.0 and 1.1 which have known vulnerabilities.

FTPS State: FTP is an insecure protocol. For production apps, configure as Disabled or FtpsOnly (FTP over TLS).

IP Restrictions: allows defining allowlist or denylist rules for IPs to access the app and/or SCM (Kudu).

# Configure IP Restriction: only office IPs
az webapp config access-restriction add \
--resource-group "rg-webapp" \
--name "ecommerce-admin" \
--priority 100 \
--action Allow \
--ip-address "200.200.200.0/24" \
--name "escritorio-sp"

# Block everything else (deny all rule)
az webapp config access-restriction add \
--resource-group "rg-webapp" \
--name "ecommerce-admin" \
--priority 2147483647 \
--action Deny \
--ip-address "Any" \
--name "deny-all"

Key Vault References​

Instead of storing secrets directly in App Settings, use Key Vault References. App Service resolves the reference at runtime and injects the secret value as an environment variable.

# Key Vault Reference format as App Setting:
# @Microsoft.KeyVault(SecretUri=https://kv-prod.vault.azure.net/secrets/db-password/version)
# or without version (always gets the latest):
# @Microsoft.KeyVault(VaultName=kv-prod;SecretName=db-password)

az webapp config appsettings set \
--resource-group "rg-webapp" \
--name "ecommerce-api" \
--settings \
DB_PASSWORD="@Microsoft.KeyVault(VaultName=kv-ecommerce-prod;SecretName=db-password)"

For Key Vault References to work, App Service needs a Managed Identity and the identity needs the Key Vault Secrets User role on the Key Vault.


8. Decision Making​

Code vs. Container​

SituationChoiceReason
App in runtime supported by AzureCode (standard runtime)Simpler, no container overhead
Specific system dependenciesCustom ContainerFull control over environment
Legacy app migrationCustom ContainerEncapsulate old dependencies
Multiple components in same processCustom ContainerDocker Compose via App Service
Modern .NET, Node.js, Python, Java appCodeNative runtimes have excellent integration

When to use Deployment Slots​

SituationApproachReason
Code deploy to productionStaging Slot + SwapZero downtime, easy rollback
Test new configuration in prodSlot with slot settingsConfiguration isolated per slot
A/B testingMultiple slots + traffic splitSplit traffic between slots
Urgent hotfix in productionDeploy directly to prod slotNo time for staging
Basic tierDeploy directlySlots not available in Basic

App Settings vs. Key Vault References​

DataApproachReason
Non-sensitive external API URLDirect App SettingSimple, no overhead
Database connection stringKey Vault ReferenceSensitive credential
Third-party API keyKey Vault ReferenceSecret that shouldn't appear in logs
Feature flagDirect App SettingNot sensitive
Private certificateKey Vault ReferenceMaximum protection

9. Best Practices​

Never put secrets directly in App Settings visible in the portal. Use Key Vault References for all sensitive information. App Settings are visible to anyone with Contributor access to App Service, and may appear in audit logs.

Configure Application Insights from the start. Application Insights provides performance telemetry, exception tracking, request metrics and much more. It's much harder to troubleshoot issues retroactively without historical telemetry.

Use slot settings for configurations that vary by environment. Database connection string should be slot-specific: the staging slot uses the staging database, and doesn't switch to the production database when you do the swap.

Enable HTTPS Only and minimum TLS 1.2 in all production apps. These are basic security configurations that should be standard. Configure via IaC to ensure they're never forgotten.

Disable FTP in production. FTP is an unencrypted protocol. If FTP deploy is not used, disable it completely. Use Git, GitHub Actions, Azure DevOps or ZIP deploy.

Configure healthcheck endpoint. App Service has a configurable healthcheck path. If the app fails to respond to this endpoint, App Service automatically restarts unhealthy instances. Configure a /health endpoint that checks the actual application health.

# Configure healthcheck
az webapp config set \
--resource-group "rg-webapp" \
--name "ecommerce-api" \
--health-check-path "/api/health"

10. Common Errors​

ErrorWhy it happensHow to avoid
App Setting modified in production causing downtimeApp Settings changes restart the appUse slots and change settings in staging, then swap
App name already in use globallyazurewebsites.net is globalUse unique suffix like name-company-prod
Container with latest tag doesn't update automaticallyAzure doesn't monitor the registryUse CI/CD with versioned tags and automatic restart
App Settings with secrets in plain textConfiguration convenienceAlways use Key Vault References for sensitive data
Slot swap switched database configurationConnection string not marked as slot-specificMark all environment-specific config as slot settings
Always On disabled causing cold startsComes disabled by defaultEnable Always On in any production app (Basic+)
Log streaming shows error but not detailsApplication logging not enabledEnable logs at Information or Verbose level
App doesn't respond after container deployHealthcheck failed but app still startingConfigure adequate startup probe timeout

The most costly production error​

Deploying new code directly to the production slot without using staging slots. If the new code has a critical bug that didn't appear in tests, you need to rollback via new deploy (which takes time and may have issues). With deployment slots, rollback is a single swap command that takes seconds.


11. Operation and Maintenance​

Diagnostics and monitoring​

# View app state
az webapp show \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "{Estado: state, URL: defaultHostName, OutboundIPs: outboundIpAddresses}" \
--output json

# View logs in real time
az webapp log tail \
--resource-group "rg-webapp" \
--name "ecommerce-frontend"

# Download logs
az webapp log download \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--log-file ./app-logs-$(date +%Y%m%d).zip

# View app outbound IPs (for database whitelist)
az webapp show \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "outboundIpAddresses" \
--output tsv

# View all App Settings (without showing values)
az webapp config appsettings list \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "[].name" \
--output table

# Diagnose VNet Integration issues
az webapp vnet-integration list \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--output table

Important limits​

ResourceFreeBasicStandardPremium v3
App storage1 GB10 GB50 GB250 GB
CPU per day60 minUnlimitedUnlimitedUnlimited
Deployment Slots00520
Custom Domains0500500500
VNet IntegrationNoNoYesYes
Always OnNoYesYesYes
Auto ScaleNoManualYesYes

12. Integration and Automation​

GitHub Actions for continuous deployment​

# .github/workflows/deploy.yml
name: Deploy to Azure App Service

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install and Build
run: |
npm ci
npm run build

- name: Deploy to Staging Slot
uses: azure/webapps-deploy@v3
with:
app-name: 'ecommerce-frontend'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_STAGING }}
slot-name: 'staging'
package: './dist'

- name: Swap Staging to Production
uses: azure/CLI@v2
with:
inlineScript: |
az webapp deployment slot swap \
--resource-group rg-webapp \
--name ecommerce-frontend \
--slot staging \
--target-slot production

Integration with Application Insights via Bicep​

// Application Insights for app monitoring
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: 'appi-ecommerce-prod'
location: 'brazilsouth'
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: logAnalyticsWorkspace.id
}
}

// Web App with Application Insights configured
resource webApp 'Microsoft.Web/sites@2022-09-01' = {
name: 'ecommerce-frontend'
location: 'brazilsouth'
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsights.properties.ConnectionString
}
{
name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
value: '~3' // Enable auto-instrumentation without modifying code
}
]
}
}
}

13. Final Summary​

Essential points:

  • An App Service is a web application hosted on an App Service Plan; you manage code and configurations, Microsoft manages the server
  • App Settings are environment variables injected at runtime; changes cause app restart
  • Deployment Slots allow deploying code to a separate slot (staging) and then do slot swap to production without downtime
  • Slot Settings mark App Settings and Connection Strings as slot-specific, preventing them from switching during swap
  • Always On keeps the app process active; without it, cold starts happen after inactivity; available only in Basic and above
  • The App Service name composes the URL {name}.azurewebsites.net which must be globally unique

Critical differences:

  • App Settings vs. Connection Strings: Connection Strings receive automatic prefixes (SQLAZURECONNSTR_, etc.) and are managed separately in the portal, but both reach the app as environment variables
  • Code vs. Container: Code uses Microsoft-managed runtimes; Container allows any environment but you manage the image
  • Slot Settings vs. normal App Settings: Slot settings stay "pinned" to the slot during swaps; normal App Settings are switched with the code during swap
  • File System Logging vs. Blob Logging: File System is temporary (disables in 12h); Blob is persistent for long retention

What needs to be remembered for AZ-104:

  • Deployment Slots are available from Standard tier (5 slots) and Premium v3 (20 slots); they don't exist in Basic
  • Key Vault References in App Settings require Managed Identity with Key Vault Secrets User role
  • az webapp log tail for real-time log streaming
  • az webapp deployment slot swap --slot staging --target-slot production to perform swap
  • Configurations marked as slot settings are not switched during swap; they remain in the slot where they were configured
  • HTTPS Only and minimum TLS 1.2 are security configurations that should be standard in production
  • The SCM (Kudu) is at {name}.scm.azurewebsites.net and offers SSH access to container, file browser and diagnostics