Azure Logic Apps — CI/CD: GitHub Actions for Logic Apps

File-Based Workflow Deployment, Parameter Files, and Automation


Introduction

Implementing CI/CD (Continuous Integration/Continuous Deployment) for Azure Logic Apps enables automated, repeatable, and reliable deployments across environments. Using GitHub Actions, you can version control your workflows and automatically deploy changes when code is merged.

This guide covers:

  • Export and structure — How to properly structure Logic App files
  • GitHub Actions setup — Complete workflow configuration
  • Environment management — Dev, staging, production parameter handling
  • Advanced patterns — Rolling deployments, approvals, testing
  • Best practices — Production-ready configurations

Export and Structure Logic Apps

Export Workflow to Files

# Export workflow definition
az logic workflow export \
  --resource-group my-rg \
  --name my-logic-app \
  --output-file ./logicapps/workflow.json

# Export connections (for Standard Logic Apps)
az logic workflow connection list \
  --resource-group my-rg \
  --name my-logic-app

# Export parameters
az logicapp config appsettings list \
  --name my-logic-app \
  --resource-group my-rg

Recommended Folder Structure

logic-apps/
├── workflows/
│   ├── order-processing.json
│   └── invoice-handler.json
├── parameters/
│   ├── dev.json
│   ├── staging.json
│   └── prod.json
├── connections/
│   └── api-connections.json
├── schemas/
│   ├── order-schema.json
│   └── invoice-schema.json
└── deploy.sh

Workflow File Structure

{
  "definition": {
    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "apiBaseUrl": {
        "type": "string",
        "defaultValue": "https://dev-api.example.com"
      },
      "connectionString": {
        "type": "securestring"
      }
    },
    "triggers": {
      "When_a_HTTP_request_is_received": {
        "type": "Request",
        "kind": "Http",
        "inputs": {
          "schema": {
            "type": "object",
            "properties": {
              "orderId": { "type": "string" }
            }
          }
        }
      }
    },
    "actions": {
      "Process_Order": {
        "type": "Http",
        "inputs": {
          "uri": "@parameters('apiBaseUrl')/orders",
          "method": "POST",
          "body": "@triggerBody()"
        }
      }
    }
  },
  "parameters": []
}

GitHub Actions for Logic Apps

Basic Deployment Workflow

name: Deploy Logic App

on:
  push:
    branches: [main]
    paths: ['logic-apps/**']

env:
  AZURE_RG: 'my-resource-group'
  LOGIC_APP_NAME: 'order-processing'

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Azure Login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
      
      - name: Deploy Logic App Workflow
        uses: azure/logic-apps-deploy@v1
        with:
          resource-group: ${{ env.AZURE_RG }}
          logic-app-name: ${{ env.LOGIC_APP_NAME }}
          workflow-file: './logic-apps/workflows/order-processing.json'
      
      - name: Verify deployment
        run: |
          az logic workflow show \
            --resource-group ${{ env.AZURE_RG }} \
            --name ${{ env.LOGIC_APP_NAME }} \
            --query "state"

Complete Multi-Environment Workflow

name: Deploy Logic App to Environment

on:
  push:
    branches: [main]
    paths: ['logic-apps/**']
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        type: choice
        options:
          - dev
          - staging
          - prod

env:
  LOGIC_APP_NAME: 'order-processing'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Validate JSON structure
        run: |
          for file in logic-apps/workflows/*.json; do
            echo "Validating $file..."
            cat "$file" | python3 -m json.tool > /dev/null
            if [ $? -ne 0 ]; then
              echo "Invalid JSON in $file"
              exit 1
            fi
          done
      
      - name: Validate workflow schema
        run: |
          # Check required fields
          jq -e '.definition.triggers' logic-apps/workflows/*.json
          jq -e '.definition.actions' logic-apps/workflows/*.json

  deploy:
    needs: validate
    runs-on: ubuntu-latest
    environment: 
      name: ${{ github.event.inputs.environment || 'dev' }}
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Azure Login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
      
      - name: Set environment variables
        run: |
          echo "ENVIRONMENT=${{ github.event.inputs.environment || 'dev' }}" >> $GITHUB_ENV
          echo "RG_NAME=$(echo ${{ secrets.RESOURCE_GROUP_${{ upper(github.event.inputs.environment || 'dev') }}})" >> $GITHUB_ENV
      
      - name: Deploy Logic App
        run: |
          az logic workflow set \
            --resource-group ${{ env.RG_NAME }} \
            --name ${{ env.LOGIC_APP_NAME }} \
            --definition @logic-apps/workflows/order-processing.json
      
      - name: Update App Settings
        run: |
          # Read parameters file
          PARAM_FILE="logic-apps/parameters/${{ env.ENVIRONMENT }}.json"
          
          # Set settings from parameters
          jq -r '.parameters | to_entries[] | "\(.key)=\(.value.value)"' "$PARAM_FILE" | while read key value; do
            az logicapp config appsettings set \
              --name ${{ env.LOGIC_APP_NAME }} \
              --resource-group ${{ env.RG_NAME }} \
              --settings "$key=$value"
          done
      
      - name: Verify deployment
        run: |
          STATE=$(az logic workflow show \
            --resource-group ${{ env.RG_NAME }} \
            --name ${{ env.LOGIC_APP_NAME }} \
            --query "state" -o tsv)
          
          if [ "$STATE" != "Enabled" ]; then
            echo "Warning: Logic App state is $STATE"
          fi

Environment-Specific Parameters

Parameter File Structure

{
  "environment": "dev",
  "logicApp": {
    "name": "order-processing",
    "region": "eastus"
  },
  "parameters": {
    "apiBaseUrl": {
      "value": "https://dev-api.example.com"
    },
    "storageAccount": {
      "value": "devstorageaccount"
    },
    "keyVaultName": {
      "value": "dev-key-vault"
    },
    "logAnalyticsWorkspace": {
      "value": "dev-la-workspace"
    }
  },
  "secrets": {
    "connectionString": {
      "keyVault": {
        "name": "dev-key-vault",
        "secretName": "storage-connection-string"
      }
    },
    "apiKey": {
      "keyVault": {
        "name": "dev-key-vault",
        "secretName": "external-api-key"
      }
    }
  }
}

Different Environment Files

// parameters/dev.json
{
  "environment": "dev",
  "parameters": {
    "apiBaseUrl": { "value": "https://dev-api.example.com" },
    "logLevel": { "value": "verbose" },
    "retryCount": { "value": 3 }
  }
}

// parameters/staging.json
{
  "environment": "staging",
  "parameters": {
    "apiBaseUrl": { "value": "https://staging-api.example.com" },
    "logLevel": { "value": "information" },
    "retryCount": { "value": 2 }
  }
}

// parameters/prod.json
{
  "environment": "prod",
  "parameters": {
    "apiBaseUrl": { "value": "https://api.example.com" },
    "logLevel": { "value": "error" },
    "retryCount": { "value": 1 }
  }
}

Deployment Script with Parameters

#!/bin/bash
# deploy.sh - Deploy Logic App with environment-specific parameters

set -e

ENVIRONMENT=${1:-dev}
RESOURCE_GROUP=${2:-my-rg}
LOGIC_APP_NAME=${3:-order-processing}
PARAM_FILE="logic-apps/parameters/${ENVIRONMENT}.json"

echo "Deploying to $ENVIRONMENT environment..."

# Validate parameter file exists
if [ ! -f "$PARAM_FILE" ]; then
    echo "Error: Parameter file not found: $PARAM_FILE"
    exit 1
fi

# Get parameters from JSON
API_BASE_URL=$(jq -r '.parameters.apiBaseUrl.value' "$PARAM_FILE")
STORAGE_ACCOUNT=$(jq -r '.parameters.storageAccount.value' "$PARAM_FILE")
KEY_VAULT=$(jq -r '.parameters.keyVaultName.value' "$PARAM_FILE")

# Deploy workflow definition
echo "Deploying workflow..."
az logic workflow set \
  --resource-group "$RESOURCE_GROUP" \
  --name "$LOGIC_APP_NAME" \
  --definition @logic-apps/workflows/order-processing.json \
  --output none

# Update app settings with parameters
echo "Updating app settings..."
az logicapp config appsettings set \
  --name "$LOGIC_APP_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --settings "API_BASE_URL=$API_BASE_URL" \
             "STORAGE_ACCOUNT=$STORAGE_ACCOUNT" \
             "KEY_VAULT_NAME=$KEY_VAULT"

# Reference Key Vault secrets
echo "Configuring Key Vault references..."
KV_SECRETS=$(jq -r '.secrets | to_entries[] | .key' "$PARAM_FILE")
for secret in $KV_SECRETS; do
  KV_NAME=$(jq -r ".secrets.$secret.keyVault.name" "$PARAM_FILE")
  SECRET_NAME=$(jq -r ".secrets.$secret.keyVault.secretName" "$PARAM_FILE")
  
  az logicapp config appsettings set \
    --name "$LOGIC_APP_NAME" \
    --resource-group "$RESOURCE_GROUP" \
    --settings "$secret=@Microsoft.KeyVault(SecretUri=https://${KV_NAME}.vault.azure.net/secrets/${SECRET_NAME})"
done

# Enable diagnostic logging
echo "Configuring diagnostics..."
az monitor diagnostic-settings create \
  --name "${LOGIC_APP_NAME}-diagnostics" \
  --resource-group "$RESOURCE_GROUP" \
  --resource "$LOGIC_APP_NAME" \
  --resource-type "Microsoft.Logic/workflows" \
  --workspace "$(jq -r '.parameters.logAnalyticsWorkspace.value' "$PARAM_FILE")" \
  --logs '[
    {"category": "WorkflowRuntime", "enabled": true},
    {"category": "WorkflowTrigger", "enabled": true}
  ]' \
  --metrics '[
    {"category": "Runs", "enabled": true},
    {"category": "Actions", "enabled": true}
  ]'

echo "Deployment completed successfully!"

Advanced Deployment Patterns

Blue-Green Deployment

name: Blue-Green Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to staging slot
        run: |
          az logicapp deployment slot create \
            --name my-logic-app \
            --resource-group my-rg \
            --slot staging
      
          az logicapp deployment swap \
            --name my-logic-app \
            --resource-group my-rg \
            --slot staging \
            --target-slot production

Approval Workflow

name: Production Deploy with Approval

on:
  push:
    branches: [main]
    paths: ['logic-apps/**']

jobs:
  deploy-dev:
    runs-on: ubuntu-latest
    environment: dev
    steps:
      - run: echo "Deploying to dev..."

  deploy-staging:
    needs: deploy-dev
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - run: echo "Deploying to staging..."

  deploy-prod:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: 
      name: prod
      required: true
    steps:
      - run: echo "Deploying to production..."

Testing Logic Apps in CI/CD

name: Test Logic App

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Run integration tests
        run: |
          # Start ngrok for webhook testing
          ./start-ngrok.sh
          
          # Update trigger to use ngrok URL
          jq '.definition.triggers.HttpTrigger.inputs.staticBaseUrl = "'$NGROK_URL'"' \
             workflow.json > workflow-updated.json
          
          az logic workflow create \
            --resource-group test-rg \
            --name test-logic-app \
            --definition @workflow-updated.json
          
          # Trigger the workflow
          curl -X POST $NGROK_URL/webhook \
            -H "Content-Type: application/json" \
            -d '{"test": "data"}'
          
          # Wait and check results
          sleep 10
          
          # Verify run history
          az logic workflow run list \
            --resource-group test-rg \
            --name test-logic-app \
            --query "[0].status"

Best Practices

PracticeDescription
Use Standard Logic AppsBetter for CI/CD with file-based deployment
Separate workflow and parametersStore parameters in separate files
Version control all filesInclude connections, schemas, parameters
Validate before deployJSON schema validation in pipeline
Use Key Vault for secretsNever store secrets in code
Implement approval gatesRequire manual approval for prod
Test in isolated environmentUse separate test resource group
Enable diagnosticsSend logs to Log Analytics
Use deployment slotsZero-downtime deployments
Rollback capabilityKeep previous versions for quick rollback

Secrets Management

# Don't do this - secrets in code
settings:
  API_KEY: "my-secret-key"  # BAD!

# Do this - reference Key Vault
settings:
  API_KEY: "@Microsoft.KeyVault(SecretUri=...)"

# In GitHub Actions
- name: Deploy with Key Vault
  run: |
    az logicapp config appsettings set \
      --name my-logic-app \
      --resource-group my-rg \
      --settings "API_KEY=@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret)"

Monitoring Deployments

Check Deployment Status

# Get workflow runs
az logic workflow run list \
  --resource-group my-rg \
  --name my-logic-app \
  --query "[].{Status:status, StartTime:startTime, EndTime:endTime}"

# Get action details
az logic workflow run show \
  --resource-group my-rg \
  --name my-logic-app \
  --run-name 0858526123446791234567890 \
  --query "properties.actions"

Alert on Failed Deployments

- name: Alert on failure
  if: failure()
  run: |
    az monitor metrics alert create \
      --name "logic-app-failure" \
      --resource-group my-rg \
      --condition "count run-failed > 0" \
      --description "Logic App deployment failed"

Azure Integration Hub - Advanced Level