Blue/Green Deployments with Azure App Service + GitHub Actions

Stephen Burke

How to set up blue/green deployments with Azure App Service and GitHub Actions

Introduction

Setting up blue/green deployments with Azure App Service and GitHub Actions is a great way to ensure that your application is always available to users, even during deployment. It is especially useful for applications that require zero downtime during deployment, such as e-commerce sites or applications that handle sensitive data.

This document outlines how to set up a Blue/Green deployment strategy using Azure App Service Slots and GitHub Actions, including a dedicated Maintenance Page that can be activated at any time with zero downtime.

✅ Goals


🧱 Setup Requirements

In Azure:

In GitHub:

Add these repository secrets:

Secret NamePurpose
AZURE_CLIENT_IDApp registration/client ID
AZURE_TENANT_IDTenant ID
AZURE_SUBSCRIPTION_IDAzure subscription
AZURE_RESOURCE_GROUPThe App Service’s resource group

📦 GitHub Actions Workflow File

.github/workflows/build-and-deploy.yml

Key features:

name: Build and Deploy to Azure Web App

on:
  push:
    branches:
      - main
  workflow_dispatch:
    inputs:
      slot:
        description: "Slot to deploy to (for manual deploy or swap)"
        required: true
        default: staging
        type: choice
        options:
          - production
          - staging
          - maintenance
          - swap-to-production

permissions:
  id-token: write
  contents: read

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

    steps:
      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - uses: actions/checkout@v3

      - name: Cache Bun deps
        uses: actions/cache@v3
        with:
          path: ~/.bun/install/cache
          key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb') }}

      - name: Install Bun
        run: |
          curl -fsSL https://bun.sh/install | bash
          echo "$HOME/.bun/bin" >> $GITHUB_PATH

      - name: Install deps + Prisma + Build
        run: |
          bun install
          npx prisma generate
          echo "NEXT_DISABLE_ESLINT_PLUGIN=true" >> $GITHUB_ENV
          bun run build

      - name: Zip for deployment
        run: |
          rm -rf deploy nextjs-app.zip
          mkdir deploy
          cp -r .next public node_modules package.json bun.lockb next.config.js deploy/
          cd deploy
          zip -r ../nextjs-app.zip .

      - name: Deploy to staging
        if: github.event_name == 'push' || github.event.inputs.slot == 'staging'
        uses: azure/webapps-deploy@v2
        with:
          app-name: event-planning-trpc
          slot-name: staging
          package: nextjs-app.zip

      - name: Deploy to maintenance
        if: github.event.inputs.slot == 'maintenance'
        uses: azure/webapps-deploy@v2
        with:
          app-name: event-planning-trpc
          slot-name: maintenance
          package: nextjs-app.zip

      - name: Swap staging → production
        if: github.event.inputs.slot == 'swap-to-production'
        uses: azure/CLI@v1
        with:
          azcliversion: 2.30.0
          inlineScript: |
            az webapp deployment slot swap \
              --name event-planning-trpc \
              --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \
              --slot staging \
              --target-slot production

🚧 Maintenance Mode Implementation (Next.js)

In layout.tsx:

import MaintenancePage from './maintenance/page';

export default function RootLayout({ children }) {
  const isUnderMaintenance = process.env.SHOW_MAINTENANCE_PAGE === 'true';

  return (
    <html lang="en">
      <body>
        {isUnderMaintenance ? <MaintenancePage /> : children}
      </body>
    </html>
  );
}

In maintenance/page.tsx

import { notFound } from 'next/navigation';

export default function MaintenancePage() {
  const isUnderMaintenance = process.env.SHOW_MAINTENANCE_PAGE === 'true';

  if (!isUnderMaintenance) {
    return notFound();
  } else {
    return (
      <main className="min-h-screen flex items-center justify-center text-center">
        <div>
          <h1 className="text-3xl font-bold">🚧 Site Under Maintenance</h1>
          <p className="mt-4">We’ll be back shortly!</p>
        </div>
      </main>
    );
  }
}

Set this env var in slot config:

Make sure to check the Deployment slot setting checkbox

SlotSHOW_MAINTENANCE_PAGE
maintenancetrue
stagingfalse
productionfalse

Then you can:


🧭 Manual Deploys with workflow_dispatch

This workflow supports manual deployments and slot swaps using GitHub’s workflow_dispatch input.

It lets you:

🧪 How to Use It

  1. Go to your repo on GitHub.com
  2. Navigate to the Actions tab
  3. Select the Build and Deploy to Azure Web App workflow
  4. Click the “Run workflow” button (top-right)
  5. Choose a value from the Slot dropdown:
  1. Click Run workflow

ℹ️ Tip: This is useful for hotfixes, testing staging before a swap, or triggering a maintenance window without code changes.


🧪 Testing & Troubleshooting


✅ Summary

You’re now fully set up for:


Thank you for reading! If you enjoyed this post, feel free to share it with your friends and colleagues. For more insights on web development, technology trends, and digital innovation, stay tuned to this blog.

Prev:

Trying Out Lynx.js: A Promising Start, But Not Quite There Yet link
Next:

Tailwind Breakpoint Helper link