Azure
beginner
30-40 minutes

App Service Deployment

Deploy web applications to Azure App Service - Microsoft's fully managed platform for web apps and APIs.

What you'll accomplish

Your web application running on Azure App Service with a public URL

Prerequisites

  • Azure account with subscription
  • Azure CLI installed
  • Web application code

What is Azure App Service?

App Service is Azure's fully managed platform for hosting web applications:

  • Fully managed - No server maintenance
  • Auto-scaling - Scale based on demand
  • Built-in CI/CD - Deploy from Git, GitHub, Azure DevOps
  • SSL included - Free managed certificates
  • Multiple runtimes - Node.js, Python, .NET, Java, PHP
Your Code → App Service → Azure Infrastructure
              │
              ├── Auto-scaling
              ├── Load balancing
              ├── SSL/TLS
              └── Monitoring

App Service vs Other Options

ServiceBest ForContainers
App ServiceWeb apps, APIsOptional
Container AppsMicroservicesRequired
FunctionsEvent-drivenOptional
VMsFull controlOptional
Note

App Service is the recommended starting point for most web applications on Azure.

Step 1: Create an App Service Plan

An App Service Plan defines the compute resources for your apps.

Pricing Tiers

TierUse CaseFeatures
F1 (Free)Testing60 min/day, no custom domains
D1 (Shared)Dev/testCustom domains, 240 min/day
B1 (Basic)Light productionCustom domains, SSL, 1.75 GB RAM
S1 (Standard)ProductionAuto-scale, slots, backups
P1V3 (Premium)High performanceMore CPU/RAM, VNet

Create Plan via CLI

bash
# Create Linux App Service Plan
az appservice plan create \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku B1 \
  --is-linux \
  --location eastus

# For Windows apps, omit --is-linux
az appservice plan create \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku B1 \
  --location eastus

Step 2: Create the Web App

List Available Runtimes

bash
# Linux runtimes
az webapp list-runtimes --os-type linux

# Windows runtimes
az webapp list-runtimes --os-type windows

Common runtimes:

  • Node.js: NODE:20-lts, NODE:18-lts
  • Python: PYTHON:3.12, PYTHON:3.11
  • .NET: DOTNETCORE:8.0, DOTNETCORE:7.0
  • Java: JAVA:17-java17, JAVA:11-java11
  • PHP: PHP:8.3, PHP:8.2

Create Web App

bash
az webapp create \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --plan myapp-plan \
  --runtime "NODE:20-lts"
Note

The app name must be globally unique - it becomes myapp-unique-name.azurewebsites.net.

Verify Creation

bash
# Get app URL
az webapp show \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --query defaultHostName \
  --output tsv

# Open in browser
az webapp browse --name myapp-unique-name --resource-group myapp-rg

Step 3: Deploy Your Application

Option A: Quick Deploy with az webapp up

The simplest deployment method:

bash
# Navigate to your project directory
cd /path/to/your/app

# Deploy (creates plan and app if needed)
az webapp up \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --runtime "NODE:20-lts" \
  --sku B1 \
  --location eastus

Option B: ZIP Deployment

bash
# Create ZIP of your app
zip -r app.zip . -x "node_modules/*" -x ".git/*"

# Deploy ZIP
az webapp deployment source config-zip \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --src app.zip

Option C: Git Deployment

bash
# Get Git deployment URL
az webapp deployment source config-local-git \
  --name myapp-unique-name \
  --resource-group myapp-rg

# Add as remote
git remote add azure <deployment-url>

# Push to deploy
git push azure main

Option D: GitHub Actions

  1. Go to Deployment Center in Azure Portal
  2. Select GitHub as source
  3. Choose repository and branch
  4. Azure generates workflow file
yaml
# .github/workflows/azure.yml (auto-generated)
name: Deploy to Azure Web App

on:
  push:
    branches: [main]

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

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

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

      - name: Deploy to Azure
        uses: azure/webapps-deploy@v2
        with:
          app-name: myapp-unique-name
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
          package: .

Step 4: Configure Application Settings

Environment Variables

bash
# Set environment variable
az webapp config appsettings set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --settings NODE_ENV=production API_KEY=your-key

# View settings
az webapp config appsettings list \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --output table

# Delete setting
az webapp config appsettings delete \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --setting-names API_KEY

Connection Strings

bash
# Set connection string
az webapp config connection-string set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --connection-string-type SQLAzure \
  --settings DefaultConnection="Server=..."

Application Configuration

bash
# Set startup command
az webapp config set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --startup-file "npm start"

# Set Node.js version
az webapp config set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --linux-fx-version "NODE|20-lts"

# Enable WebSockets
az webapp config set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --web-sockets-enabled true

# Always On (keeps app warm)
az webapp config set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --always-on true

Step 5: View Logs

Stream Logs

bash
# Enable logging
az webapp log config \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --application-logging filesystem \
  --detailed-error-messages true \
  --failed-request-tracing true \
  --web-server-logging filesystem

# Stream live logs
az webapp log tail \
  --name myapp-unique-name \
  --resource-group myapp-rg

Download Logs

bash
# Download log files
az webapp log download \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --log-file webapp_logs.zip

Deployment Slots (Standard tier+)

Test changes before going live:

bash
# Create staging slot
az webapp deployment slot create \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --slot staging

# Deploy to staging
az webapp deployment source config-zip \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --slot staging \
  --src app.zip

# Swap staging to production
az webapp deployment slot swap \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --slot staging \
  --target-slot production

Slot Settings

Settings that stay with the slot (not swapped):

bash
# Mark setting as slot-specific
az webapp config appsettings set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --slot staging \
  --slot-settings SLOT_NAME=staging

Auto-Scaling (Standard tier+)

bash
# Enable autoscale
az monitor autoscale create \
  --resource-group myapp-rg \
  --name myapp-autoscale \
  --resource /subscriptions/.../resourceGroups/myapp-rg/providers/Microsoft.Web/serverfarms/myapp-plan \
  --min-count 1 \
  --max-count 5 \
  --count 1

# Add CPU-based rule
az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "Percentage CPU > 70 avg 5m" \
  --scale out 1

az monitor autoscale rule create \
  --resource-group myapp-rg \
  --autoscale-name myapp-autoscale \
  --condition "Percentage CPU < 30 avg 5m" \
  --scale in 1

Custom Domains

Add Custom Domain

bash
# Add custom domain
az webapp config hostname add \
  --webapp-name myapp-unique-name \
  --resource-group myapp-rg \
  --hostname www.example.com

Configure DNS

At your DNS provider, add:

TypeNameValue
CNAMEwwwmyapp-unique-name.azurewebsites.net
TXTasuid.www(verification ID from Azure)

For apex domain:

TypeNameValue
A@App Service IP
TXTasuid(verification ID)

Free Managed SSL

bash
# Create managed certificate
az webapp config ssl create \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --hostname www.example.com

# Bind certificate
az webapp config ssl bind \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --certificate-thumbprint THUMBPRINT \
  --ssl-type SNI

# Enforce HTTPS
az webapp update \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --https-only true

Container Deployment

Deploy Docker containers to App Service:

bash
# Create web app for containers
az webapp create \
  --name myapp-container \
  --resource-group myapp-rg \
  --plan myapp-plan \
  --deployment-container-image-name nginx

# Use Azure Container Registry
az webapp config container set \
  --name myapp-container \
  --resource-group myapp-rg \
  --container-image-name myregistry.azurecr.io/myapp:latest \
  --container-registry-url https://myregistry.azurecr.io \
  --container-registry-user myregistry \
  --container-registry-password PASSWORD

Enable Continuous Deployment

bash
# Enable CD webhook
az webapp deployment container config \
  --name myapp-container \
  --resource-group myapp-rg \
  --enable-cd true

Backup and Restore (Standard tier+)

bash
# Create storage for backups
az storage account create \
  --name myappbackups \
  --resource-group myapp-rg \
  --sku Standard_LRS

# Create container
az storage container create \
  --name backups \
  --account-name myappbackups

# Get SAS URL
EXPIRY=$(date -d "+1 year" +%Y-%m-%d)
SAS_URL=$(az storage container generate-sas \
  --account-name myappbackups \
  --name backups \
  --permissions rwdl \
  --expiry $EXPIRY \
  --output tsv)

# Configure backup
az webapp config backup create \
  --webapp-name myapp-unique-name \
  --resource-group myapp-rg \
  --container-url "https://myappbackups.blob.core.windows.net/backups?$SAS_URL" \
  --frequency 1d \
  --retain-one true

VNet Integration (Premium tier)

Connect to private resources:

bash
# Create VNet integration
az webapp vnet-integration add \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --vnet myapp-vnet \
  --subnet app-subnet

Health Checks

bash
# Configure health check
az webapp config set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --generic-configurations '{"healthCheckPath": "/api/health"}'

Troubleshooting

Application Won't Start

bash
# Check logs
az webapp log tail --name myapp-unique-name --resource-group myapp-rg

# SSH into container (Linux)
az webapp ssh --name myapp-unique-name --resource-group myapp-rg

# Check if port is correct
# App must listen on PORT environment variable

Slow Performance

bash
# Check metrics
az monitor metrics list \
  --resource /subscriptions/.../resourceGroups/myapp-rg/providers/Microsoft.Web/sites/myapp-unique-name \
  --metric "CpuPercentage,MemoryPercentage" \
  --output table

# Scale up
az appservice plan update \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku S1

502 Bad Gateway

  1. Check application logs for errors
  2. Verify startup command is correct
  3. Check health check endpoint works
  4. Increase startup time:
bash
az webapp config appsettings set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --settings WEBSITES_CONTAINER_START_TIME_LIMIT=1800

Node.js Configuration

json
// package.json
{
  "scripts": {
    "start": "node server.js"
  },
  "engines": {
    "node": "20.x"
  }
}
javascript
// server.js - Use PORT env variable
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Cost Optimization

  1. Use Free/Shared for dev - F1 and D1 tiers
  2. Scale down when not needed - Stop apps at night
  3. Use deployment slots wisely - Only on Standard+
  4. Reserved instances - Up to 55% savings for 1-3 years
  5. Delete unused resources - Plans, slots, backups
bash
# Stop app (saves compute costs)
az webapp stop --name myapp-unique-name --resource-group myapp-rg

# Start app
az webapp start --name myapp-unique-name --resource-group myapp-rg

# Delete app
az webapp delete --name myapp-unique-name --resource-group myapp-rg
Note

Checkpoint: Deploy your application to App Service, configure environment variables, and verify it's accessible at the azurewebsites.net URL.