Skip to main content

Zero-Downtime Deployment in Azure App Service: Deployment Slots, Health Checks and Rollbacks

Azure App Service Deployment Guide

Zero-Downtime Deployment in Azure App Service

Deployment Slots, Health Checks, Database Migrations, GitHub Actions and Rollbacks

Deploying an application should not require displaying a maintenance page, restarting the production application in front of users, or hoping that the new release starts successfully. Azure App Service provides deployment slots that allow teams to deploy, initialize, validate and test a new application version before it receives production traffic.

A properly designed slot-based deployment process separates two activities that are often incorrectly treated as one operation:

  • Deploying the release to an isolated staging environment.
  • Releasing the application by directing production traffic to the validated version.
πŸ’‘ The central idea

Do not build and initialize a new release while customers are using it. Build it, deploy it, warm it up and validate it in staging first. Only after it passes the release gates should production traffic be redirected to it.

Chapter
1

Production and Staging Slots

Every Azure App Service application has a default production slot. When the App Service plan supports deployment slots, you can create additional live environments such as staging, testing or pre-production.

Each slot has its own hostname, deployed application content and configurable settings. For example:

Production:
https://contoso-api.azurewebsites.net

Staging:
https://contoso-api-staging.azurewebsites.net

The responsibility of each slot

Slot Purpose Traffic
Production Hosts the currently approved release. Receives normal customer traffic.
Staging Hosts the candidate release for validation. Receives only deployment and test traffic.

Create a staging slot using Azure CLI

az webapp deployment slot create \
  --resource-group rg-production-app \
  --name contoso-api \
  --slot staging \
  --configuration-source contoso-api
⚠️ App Service plan requirement

Deployment slots are supported on Standard, Premium and Isolated App Service plans. The number of available slots depends on the plan tier. Confirm capacity and slot limits before designing the release workflow.


Chapter
2

Slot Swaps and Application Warm-Up

A slot swap is not the same as copying files from staging to production. Azure prepares the staging application with the target slot's applicable settings, restarts processes when required, sends warm-up requests and then redirects traffic.

What happens during a swap?

1. Apply target configuration
Azure applies the target slot's applicable configuration to the source slot.

2. Restart affected processes
Application instances restart when configuration changes require it.

3. Warm up the source slot
Azure sends requests to initialize the application on each instance.

4. Validate readiness
The platform waits for the configured warm-up process to succeed.

5. Redirect traffic
Production routing moves to the prepared application version.

Warm-up is essential for applications that perform initialization tasks such as loading configuration, establishing connection pools, compiling views, populating caches, loading machine-learning models or resolving external dependencies.

Configure a custom swap warm-up path

WEBSITE_SWAP_WARMUP_PING_PATH=/health/ready
WEBSITE_SWAP_WARMUP_PING_STATUSES=200

The warm-up endpoint should return success only when the application is actually ready to serve traffic. A shallow endpoint that always returns HTTP 200 can allow an incomplete or unusable application instance to enter production.

Preview and execute the swap

# Preview the swap and apply production configuration to staging
az webapp deployment slot swap \
  --resource-group rg-production-app \
  --name contoso-api \
  --slot staging \
  --target-slot production \
  --action preview

# Complete the swap after validation
az webapp deployment slot swap \
  --resource-group rg-production-app \
  --name contoso-api \
  --slot staging \
  --target-slot production \
  --action swap
βœ… Use swap with preview for sensitive applications

Swap with preview gives the team an additional validation window after production configuration is applied to staging but before production traffic is redirected.


Chapter
3

Sticky Application Settings

During a slot swap, some configuration values should move with the application, while environment-specific values should remain attached to their original slot. Azure calls environment-specific values deployment slot settings, commonly referred to as sticky settings.

Configure a sticky setting

az webapp config appsettings set \
  --resource-group rg-production-app \
  --name contoso-api \
  --slot staging \
  --slot-settings \
    ENVIRONMENT_NAME=staging \
    DATABASE_CONNECTION_STRING="staging-database-connection" \
    APPLICATIONINSIGHTS_CONNECTION_STRING="staging-insights-connection"
🚨 A dangerous configuration mistake

If the staging database connection is not configured correctly, the staging application may test against or modify production data. Treat slot configuration with the same level of control as application code.


Chapter
4

Database Migration Considerations

Deployment slots can make the web application deployment nearly seamless, but they do not automatically make database changes backward compatible. During a release, the old and new application versions can temporarily exist at the same time. Therefore, the database must support both versions throughout the transition.

Use the expand-and-contract pattern

Phase 1 β€” Expand
Add new tables, columns, indexes or stored procedures without removing structures used by the existing application.

Phase 2 β€” Deploy
Deploy an application version that can operate safely with both the old and new schema.

Phase 3 β€” Migrate
Backfill or transform existing data using a controlled and observable process.

Phase 4 β€” Contract
Remove obsolete columns or tables only after the previous application version can no longer receive traffic and rollback is no longer required.

Safe and unsafe database changes

Change Risk Recommended Approach
Add a nullable column Low Add it before deploying the new application.
Create a new table Low Create it as an additive migration.
Rename a column High Add a new column, copy data and remove the old column later.
Drop a column Critical Delay until rollback to the old application is no longer required.
Add a required column Medium–High Add it as nullable, backfill it, and enforce the constraint later.
⚠️ Do not run destructive migrations during application startup

Multiple App Service instances may start simultaneously, resulting in migration conflicts or database locks. Run controlled migrations as a separate pipeline step and record exactly which migration version was applied.


Chapter
5

Health Checks and Release Validation

A deployment completing successfully only proves that files or a container image reached App Service. It does not prove that the application started correctly, can connect to its dependencies or can process business requests.

Configure an application endpoint such as /health or /health/ready. A meaningful readiness endpoint can validate:

  • The application process is running.
  • Required configuration is loaded.
  • The database is reachable.
  • Critical queues, caches or messaging services are reachable.
  • The application has completed its startup sequence.

Enable App Service Health Check

az webapp config set \
  --resource-group rg-production-app \
  --name contoso-api \
  --generic-configurations '{"healthCheckPath": "/health/ready"}'

App Service Health Check regularly sends requests to the configured path on each instance. An endpoint response in the HTTP 200–299 range is treated as healthy. App Service can remove unhealthy instances from load balancing and continue checking them for recovery.

Run a staging smoke test

STAGING_URL="https://contoso-api-staging.azurewebsites.net"

curl --fail \
  --retry 12 \
  --retry-delay 10 \
  --retry-all-errors \
  "${STAGING_URL}/health/ready"

curl --fail "${STAGING_URL}/api/version"
curl --fail "${STAGING_URL}/api/smoke-test"
πŸ“Œ Liveness and readiness are different

A liveness endpoint answers, β€œIs the process alive?” A readiness endpoint answers, β€œCan this application instance safely serve traffic?” Use readiness for deployment validation and swap warm-up.


Chapter
6

Rollback Strategy

After staging is swapped into production, the previous production version moves to the staging slot. This creates a fast rollback path because the earlier release remains deployed and can be swapped back.

Rollback command

az webapp deployment slot swap \
  --resource-group rg-production-app \
  --name contoso-api \
  --slot staging \
  --target-slot production

Rollback decision process

Signal Suggested Response
Health endpoint fails Rollback immediately.
Significant increase in HTTP 5xx errors Rollback and investigate application logs.
Latency exceeds the release threshold Pause, monitor briefly and rollback if sustained.
Non-critical UI defect Evaluate business impact before rollback.
Destructive database migration already completed Follow the database recovery plan; a slot swap alone may not be safe.
🚨 A swap does not roll back the database

Application rollback and database rollback are separate operations. This is why database changes must remain backward compatible for at least the duration of the rollback window.


Chapter
7

GitHub Actions Deployment Flow

A production-ready GitHub Actions workflow should deploy to staging first, validate the release, optionally run a controlled database migration, swap staging into production and then perform post-deployment verification.

1. Checkout source code

2. Restore dependencies

3. Build and run automated tests

4. Authenticate to Azure using OpenID Connect

5. Deploy the artifact to staging

6. Wait for readiness and run smoke tests

7. Apply backward-compatible database migrations

8. Swap staging into production

9. Validate production health and monitor telemetry

Complete GitHub Actions example

name: Deploy Azure App Service

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  id-token: write

env:
  RESOURCE_GROUP: rg-production-app
  WEBAPP_NAME: contoso-api
  STAGING_SLOT: staging
  NODE_VERSION: 20
  STAGING_URL: https://contoso-api-staging.azurewebsites.net
  PRODUCTION_URL: https://contoso-api.azurewebsites.net

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Configure Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run automated tests
        run: npm test

      - name: Build application
        run: npm run build --if-present

      - name: Create deployment package
        run: |
          zip -r release.zip . \
            -x ".git/*" \
            -x ".github/*" \
            -x "release.zip"

      - name: Sign in to Azure using OpenID Connect
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy release to staging
        uses: azure/webapps-deploy@v3
        with:
          app-name: ${{ env.WEBAPP_NAME }}
          slot-name: ${{ env.STAGING_SLOT }}
          package: release.zip

      - name: Wait for staging readiness
        shell: bash
        run: |
          for attempt in {1..20}; do
            echo "Staging readiness attempt ${attempt}"

            if curl \
              --silent \
              --show-error \
              --fail \
              "${STAGING_URL}/health/ready"; then
              echo "Staging is ready."
              exit 0
            fi

            sleep 15
          done

          echo "Staging did not become ready within the expected time."
          exit 1

      - name: Run staging smoke tests
        shell: bash
        run: |
          curl --fail --show-error "${STAGING_URL}/api/version"
          curl --fail --show-error "${STAGING_URL}/api/smoke-test"

      - name: Run backward-compatible database migrations
        shell: bash
        env:
          DATABASE_CONNECTION_STRING: ${{ secrets.DATABASE_CONNECTION_STRING }}
        run: npm run database:migrate

      - name: Swap staging into production
        shell: bash
        run: |
          az webapp deployment slot swap \
            --resource-group "${RESOURCE_GROUP}" \
            --name "${WEBAPP_NAME}" \
            --slot "${STAGING_SLOT}" \
            --target-slot production

      - name: Validate production
        shell: bash
        run: |
          for attempt in {1..12}; do
            echo "Production validation attempt ${attempt}"

            if curl \
              --silent \
              --show-error \
              --fail \
              "${PRODUCTION_URL}/health/ready"; then
              echo "Production deployment is healthy."
              exit 0
            fi

            sleep 10
          done

          echo "Production validation failed."
          exit 1
πŸ” Prefer OpenID Connect

OpenID Connect allows GitHub Actions to obtain short-lived Azure credentials instead of storing a long-lived client secret or App Service publishing profile in GitHub.

For high-risk production environments, configure the GitHub production environment with required reviewers. The workflow can deploy and test staging automatically, pause for approval and then perform the production swap.


Chapter
8

Common Deployment Mistakes

Mistake Impact Prevention
Deploying directly to production Users experience startup failures or downtime. Deploy to staging and swap after validation.
No health endpoint A broken application can pass the deployment stage. Implement liveness and readiness endpoints.
Shallow health check The process appears healthy while dependencies are unavailable. Check critical dependencies with strict timeouts.
Incorrect sticky settings Staging connects to production resources or secrets move unexpectedly. Document and audit slot-specific configuration.
Destructive schema migration The previous application version can no longer run. Use expand-and-contract migrations.
No post-swap validation Production failures remain undetected. Run smoke tests and monitor telemetry after every swap.
Overwriting the old release immediately The fastest rollback option is lost. Preserve the previous version in staging during the verification window.
Using long-lived deployment secrets Credential leakage creates a security risk. Use GitHub OpenID Connect and minimum Azure permissions.

Chapter
9

Production Release Checklist

Before deployment

  • Confirm the staging slot exists and is correctly configured.
  • Review sticky settings and connection strings.
  • Confirm database migrations are backward compatible.
  • Record the current release version and deployment artifact.
  • Confirm alerts, dashboards and Application Insights are available.

Before the swap

  • Verify the staging readiness endpoint.
  • Run automated smoke tests.
  • Verify the deployed application version.
  • Check startup logs for warnings and errors.
  • Confirm the rollback owner and rollback command.

After the swap

  • Call the production health and version endpoints.
  • Monitor HTTP 5xx responses and failed requests.
  • Compare latency with the pre-deployment baseline.
  • Check database and dependency failures.
  • Preserve the previous version in staging until the release is stable.
  • Record the deployment outcome and release timestamp.
🎯 The optimal production path

Build once β†’ test the artifact β†’ deploy to staging β†’ warm up β†’ validate health β†’ apply safe migrations β†’ obtain approval β†’ swap β†’ validate production β†’ monitor β†’ preserve the previous version for rollback.


Conclusion

Zero-downtime deployment is not achieved by a slot swap alone. It is the result of combining deployment slots, application warm-up, meaningful health checks, controlled configuration, backward-compatible database changes, automated validation and a tested rollback strategy.

When these practices are built into GitHub Actions, a production release becomes a controlled and repeatable operation rather than a high-risk manual event.

Β