← Back to Guides

Deploying Logic Apps via Azure DevOps

IntermediateAzure DevOps2026-03-14

Deployment Strategies

Logic Apps deployment varies by hosting model:

Consumption

ARM/Bicep template-based deployment
DefinitionARM/Bicep templates
DeploymentARM deployment
ConnectionsAPI connection resources
ParametersARM parameters
Local devNot supported

Standard

File-based deployment (like Azure Functions)
DefinitionWorkflow files + app settings
DeploymentZIP deploy
Connectionsconnections.json + app settings
Parametersparameters.json + app settings
Local devVS Code with Functions runtime

Microsoft Reference: DevOps deployment for Logic Apps

Consumption Logic Apps Pipeline

Project Structure

logic-apps/
├── consumption/
│   ├── templates/
│   │   ├── order-processing.json       # ARM template
│   │   ├── order-processing.parameters.dev.json
│   │   └── order-processing.parameters.prod.json
│   └── connections/
│       ├── connections.dev.json
│       └── connections.prod.json
└── pipeline-consumption.yml

ARM Template for Consumption Logic App

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "logicAppName": { "type": "string" },
    "location": { "type": "string", "defaultValue": "[resourceGroup().location]" },
    "environment": { "type": "string", "allowedValues": ["dev", "test", "prod"] },
    "apiBaseUrl": { "type": "string" },
    "notificationEmail": { "type": "string" }
  },
  "resources": [
    {
      "type": "Microsoft.Logic/workflows",
      "apiVersion": "2019-05-01",
      "name": "[parameters('logicAppName')]",
      "location": "[parameters('location')]",
      "identity": {
        "type": "SystemAssigned"
      },
      "properties": {
        "state": "Enabled",
        "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": "[parameters('apiBaseUrl')]" },
            "notificationEmail": { "type": "string", "defaultValue": "[parameters('notificationEmail')]" }
          },
          "triggers": {
            "When_an_HTTP_request_is_received": {
              "type": "Request",
              "kind": "Http",
              "inputs": {
                "schema": {}
              }
            }
          },
          "actions": {}
        }
      }
    }
  ]
}

Pipeline Definition

trigger:
  branches:
    include:
      - main
  paths:
    include:
      - logic-apps/consumption/*

variables:
  - group: logic-apps-settings

stages:
  - stage: Validate
    displayName: 'Validate Templates'
    jobs:
      - job: ValidateARM
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: AzureCLI@2
            displayName: 'Validate ARM template'
            inputs:
              azureSubscription: 'dev-service-connection'
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az deployment group validate \
                  --resource-group rg-integration-dev \
                  --template-file logic-apps/consumption/templates/order-processing.json \
                  --parameters @logic-apps/consumption/templates/order-processing.parameters.dev.json

  - stage: DeployDev
    displayName: 'Deploy to Development'
    dependsOn: Validate
    jobs:
      - deployment: DeployLogicApp
        environment: 'logic-apps-dev'
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self

                - task: AzureCLI@2
                  displayName: 'Deploy Logic App'
                  inputs:
                    azureSubscription: 'dev-service-connection'
                    scriptType: 'bash'
                    scriptLocation: 'inlineScript'
                    inlineScript: |
                      az deployment group create \
                        --resource-group rg-integration-dev \
                        --template-file logic-apps/consumption/templates/order-processing.json \
                        --parameters @logic-apps/consumption/templates/order-processing.parameters.dev.json

                - task: AzureCLI@2
                  displayName: 'Verify deployment'
                  inputs:
                    azureSubscription: 'dev-service-connection'
                    scriptType: 'bash'
                    scriptLocation: 'inlineScript'
                    inlineScript: |
                      STATE=$(az logic workflow show \
                        --resource-group rg-integration-dev \
                        --name la-order-processing-dev \
                        --query 'state' -o tsv)
                      if [ "$STATE" != "Enabled" ]; then
                        echo "Logic App is not enabled. State: $STATE"
                        exit 1
                      fi
                      echo "Logic App deployed and enabled successfully."

  - stage: DeployProd
    displayName: 'Deploy to Production'
    dependsOn: DeployDev
    condition: succeeded()
    jobs:
      - deployment: DeployLogicApp
        environment: 'logic-apps-prod'
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self

                - task: AzureCLI@2
                  displayName: 'Deploy Logic App'
                  inputs:
                    azureSubscription: 'prod-service-connection'
                    scriptType: 'bash'
                    scriptLocation: 'inlineScript'
                    inlineScript: |
                      az deployment group create \
                        --resource-group rg-integration-prod \
                        --template-file logic-apps/consumption/templates/order-processing.json \
                        --parameters @logic-apps/consumption/templates/order-processing.parameters.prod.json

Standard Logic Apps Pipeline

Project Structure

logic-apps/
├── standard/
│   ├── .vscode/
│   ├── host.json
│   ├── connections.json
│   ├── parameters.json
│   ├── order-processing/
│   │   └── workflow.json
│   ├── invoice-sync/
│   │   └── workflow.json
│   └── Artifacts/
│       ├── Maps/
│       └── Schemas/
├── infra/
│   ├── main.bicep
│   ├── modules/
│   │   ├── logic-app.bicep
│   │   ├── storage.bicep
│   │   └── app-service-plan.bicep
│   └── parameters/
│       ├── dev.json
│       └── prod.json
└── pipeline-standard.yml

Pipeline Definition

trigger:
  branches:
    include:
      - main
  paths:
    include:
      - logic-apps/standard/*
      - logic-apps/infra/*

variables:
  - group: logic-app-standard-settings
  - name: workingDirectory
    value: 'logic-apps/standard'

stages:
  - stage: Build
    displayName: 'Build Logic App Package'
    jobs:
      - job: BuildPackage
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: UseDotNet@2
            inputs:
              packageType: 'sdk'
              version: '6.x'

          - script: |
              cd $(workingDirectory)
              mkdir -p $(Build.ArtifactStagingDirectory)/logic-app
              cp -r . $(Build.ArtifactStagingDirectory)/logic-app/
              # Remove local settings (not for deployment)
              rm -f $(Build.ArtifactStagingDirectory)/logic-app/local.settings.json
              rm -rf $(Build.ArtifactStagingDirectory)/logic-app/.vscode
              cd $(Build.ArtifactStagingDirectory)/logic-app
              zip -r $(Build.ArtifactStagingDirectory)/logic-app.zip .
            displayName: 'Package Logic App'

          - publish: $(Build.ArtifactStagingDirectory)/logic-app.zip
            artifact: logic-app-package

  - stage: DeployInfra
    displayName: 'Deploy Infrastructure'
    dependsOn: Build
    jobs:
      - deployment: DeployInfrastructure
        environment: 'logic-app-infra-dev'
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self

                - task: AzureCLI@2
                  displayName: 'Deploy Bicep Infrastructure'
                  inputs:
                    azureSubscription: 'dev-service-connection'
                    scriptType: 'bash'
                    scriptLocation: 'inlineScript'
                    inlineScript: |
                      az deployment group create \
                        --resource-group rg-integration-dev \
                        --template-file logic-apps/infra/main.bicep \
                        --parameters @logic-apps/infra/parameters/dev.json

  - stage: DeployApp
    displayName: 'Deploy Logic App Code'
    dependsOn: DeployInfra
    jobs:
      - deployment: DeployCode
        environment: 'logic-app-dev'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: logic-app-package

                - task: AzureFunctionApp@2
                  displayName: 'Deploy to Logic App'
                  inputs:
                    azureSubscription: 'dev-service-connection'
                    appType: 'functionApp'
                    appName: 'la-standard-dev'
                    package: '$(Pipeline.Workspace)/logic-app-package/logic-app.zip'
                    deploymentMethod: 'zipDeploy'

                - task: AzureCLI@2
                  displayName: 'Configure app settings'
                  inputs:
                    azureSubscription: 'dev-service-connection'
                    scriptType: 'bash'
                    scriptLocation: 'inlineScript'
                    inlineScript: |
                      az functionapp config appsettings set \
                        --resource-group rg-integration-dev \
                        --name la-standard-dev \
                        --settings \
                          "WORKFLOWS_SUBSCRIPTION_ID=$(subscriptionId)" \
                          "WORKFLOWS_RESOURCE_GROUP_NAME=rg-integration-dev" \
                          "WORKFLOWS_LOCATION_NAME=uksouth"

  - stage: SmokeTest
    displayName: 'Smoke Tests'
    dependsOn: DeployApp
    jobs:
      - job: RunSmokeTests
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - script: |
              # Test HTTP-triggered workflow
              RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
                -X POST "https://la-standard-dev.azurewebsites.net/api/order-processing/triggers/manual/invoke?api-version=2020-05-01-preview" \
                -H "Content-Type: application/json" \
                -d '{"orderId": "TEST-001", "test": true}')

              if [ "$RESPONSE" -ge 200 ] && [ "$RESPONSE" -lt 300 ]; then
                echo "Smoke test passed (HTTP $RESPONSE)"
              else
                echo "Smoke test failed (HTTP $RESPONSE)"
                exit 1
              fi
            displayName: 'Run smoke tests'

  - stage: DeployProd
    displayName: 'Deploy to Production'
    dependsOn: SmokeTest
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: DeployProd
        environment: 'logic-app-prod'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: logic-app-package

                - task: AzureFunctionApp@2
                  displayName: 'Deploy to Production'
                  inputs:
                    azureSubscription: 'prod-service-connection'
                    appType: 'functionApp'
                    appName: 'la-standard-prod'
                    package: '$(Pipeline.Workspace)/logic-app-package/logic-app.zip'
                    deploymentMethod: 'zipDeploy'

Connection Management

Parameterised Connections

Use app settings to manage connection strings across environments:

// connections.json
{
  "serviceProviderConnections": {
    "serviceBus": {
      "parameterValues": {
        "connectionString": "@appsetting('ServiceBus_ConnectionString')"
      },
      "serviceProvider": {
        "id": "/serviceProviders/serviceBus"
      }
    },
    "sql": {
      "parameterValues": {
        "connectionString": "@appsetting('SQL_ConnectionString')"
      },
      "serviceProvider": {
        "id": "/serviceProviders/sql"
      }
    }
  }
}

Managing Connections in Pipelines

- task: AzureCLI@2
  displayName: 'Set connection strings from Key Vault'
  inputs:
    azureSubscription: 'prod-service-connection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      # Retrieve secrets from Key Vault
      SB_CONN=$(az keyvault secret show \
        --vault-name kv-integration-prod \
        --name ServiceBusConnectionString \
        --query value -o tsv)

      SQL_CONN=$(az keyvault secret show \
        --vault-name kv-integration-prod \
        --name SqlConnectionString \
        --query value -o tsv)

      # Set as app settings (or use Key Vault references)
      az functionapp config appsettings set \
        --resource-group rg-integration-prod \
        --name la-standard-prod \
        --settings \
          "ServiceBus_ConnectionString=$SB_CONN" \
          "SQL_ConnectionString=$SQL_CONN"

Key Vault References (Preferred)

- task: AzureCLI@2
  displayName: 'Configure Key Vault references'
  inputs:
    azureSubscription: 'prod-service-connection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      az functionapp config appsettings set \
        --resource-group rg-integration-prod \
        --name la-standard-prod \
        --settings \
          "ServiceBus_ConnectionString=@Microsoft.KeyVault(VaultName=kv-integration-prod;SecretName=ServiceBusConnectionString)" \
          "SQL_ConnectionString=@Microsoft.KeyVault(VaultName=kv-integration-prod;SecretName=SqlConnectionString)"

Testing Strategies

Unit Testing Workflow Definitions

Validate workflow JSON structure:

# Validate all workflow.json files
for f in $(find . -name "workflow.json"); do
  echo "Validating $f..."
  python -c "import json; json.load(open('$f'))" || exit 1
done

Integration Testing

- stage: IntegrationTest
  dependsOn: DeployDev
  jobs:
    - job: RunIntegrationTests
      pool:
        vmImage: 'ubuntu-latest'
      steps:
        - script: |
            # Test each workflow endpoint
            for workflow in order-processing invoice-sync; do
              echo "Testing $workflow..."
              STATUS=$(curl -s -o response.json -w "%{http_code}" \
                -X POST "https://la-standard-dev.azurewebsites.net/api/$workflow/triggers/manual/invoke?api-version=2020-05-01-preview" \
                -H "Content-Type: application/json" \
                -d @test-data/$workflow-payload.json)

              if [ "$STATUS" -lt 200 ] || [ "$STATUS" -ge 300 ]; then
                echo "FAILED: $workflow returned HTTP $STATUS"
                cat response.json
                exit 1
              fi
              echo "PASSED: $workflow (HTTP $STATUS)"
            done
          displayName: 'Run integration tests'

Rollback Strategy

Deployment Slots (Standard)

resource stagingSlot 'Microsoft.Web/sites/slots@2022-09-01' = {
  parent: logicApp
  name: 'staging'
  location: location
  kind: 'functionapp,workflowapp'
  properties: {
    serverFarmId: appServicePlan.id
  }
}
# Deploy to staging slot, then swap
- task: AzureFunctionApp@2
  inputs:
    appName: 'la-standard-prod'
    slotName: 'staging'
    package: '$(Pipeline.Workspace)/logic-app-package/logic-app.zip'

- task: AzureAppServiceManage@0
  inputs:
    azureSubscription: 'prod-service-connection'
    action: 'Swap Slots'
    webAppName: 'la-standard-prod'
    sourceSlot: 'staging'
    targetSlot: 'production'

Best Practices

  1. Separate infrastructure from application code — deploy Bicep/Terraform independently
  2. Use Key Vault references for connection strings and secrets
  3. Implement deployment slots for zero-downtime production deployments
  4. Add smoke tests after each deployment stage
  5. Store workflow definitions in source control alongside application code
  6. Use environment-specific parameters via variable groups
  7. Validate templates before deployment to catch errors early
  8. Implement rollback using deployment slots or previous successful artifacts
  9. Monitor deployments with Application Insights deployment annotations
  10. Use approval gates on production environments

Official Microsoft Resources