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
| Practice | Description |
|---|---|
| Use Standard Logic Apps | Better for CI/CD with file-based deployment |
| Separate workflow and parameters | Store parameters in separate files |
| Version control all files | Include connections, schemas, parameters |
| Validate before deploy | JSON schema validation in pipeline |
| Use Key Vault for secrets | Never store secrets in code |
| Implement approval gates | Require manual approval for prod |
| Test in isolated environment | Use separate test resource group |
| Enable diagnostics | Send logs to Log Analytics |
| Use deployment slots | Zero-downtime deployments |
| Rollback capability | Keep 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