I have been building several Logic Apps recently. I wanted to fully automate the setup and configuration using Terraform. In this blog post I will be showing you how to automate the Logic App Designer to Terraform. Its a slightly different blog post than usual, rather than full blown automation from the start, in this post its going to be a multi-step process:
- Create the base Logic App Workflow deployment
- Use the Logic App Designer in Azure Portal to create the required configuration
- Use
azurerm_resource_group_template_deploymentin Terraform to automate the manual design from above
You might be wondering why we’re not going for full automation from the start. I’ve explored various methods, and this approach has proven to be the most practical (my personal thoughts anyways 🙂 for at least my use case )
Step 1: Create the base Logic App Workflow deployment
The below creates in Terraform:
- Resource Group and base Logic App Workflow
- Associated Managed Identity for the Logic App and blob storage (my Logic App will be writing to blob storage in the example)
resource "azurerm_resource_group" "tamopsrg" {
name = "tamops-logicapps-rg"
location = "UK South"
}
resource "azurerm_user_assigned_identity" "tamops-logicapp-mi" {
resource_group_name = azurerm_resource_group.tamopsrg.name
location = azurerm_resource_group.tamopsrg.location
name = "tamops-logicapp-mi"
}
resource "azurerm_logic_app_workflow" "tamops-logicapp" {
name = "tamopslogicapp1"
location = azurerm_resource_group.tamopsrg.location
resource_group_name = azurerm_resource_group.tamopsrg.name
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.tamops-logicapp-mi.id]
}
depends_on = [azurerm_user_assigned_identity.tamops-logicapp-mi]
}
resource "azurerm_storage_account" "tamopsa" {
name = "tamopslogicappsa"
resource_group_name = azurerm_resource_group.tamopsrg.name
location = azurerm_resource_group.tamopsrg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_container" "tamopsc" {
name = "tamopslogicappsc"
storage_account_name = azurerm_storage_account.tamopsa.name
container_access_type = "private"
}
resource "azurerm_role_assignment" "tamops-logicapp-mi" {
scope = azurerm_storage_account.tamopsa.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_user_assigned_identity.tamops-logicapp-mi.principal_id
}
Step 2: Use the Logic App Designer in Azure Portal to create the required configuration

For this, I created a simple workflow in the Logic app designer, but this same approach can be used for a more complex design as well
Step 3: Use azurerm_resource_group_template_deployment in Terraform to automate the manual design from above
What we will do now is export the Logic app design into Terraform using azurerm_resource_group_template_deployment
The below .json template is what I use, notice the highlights:
"definition"is where you will paste from Logic app code view in Azure"parameters"is where you will paste from Logic app code view in Azure
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"logic_app_name": {
"type": "String"
},
"location": {
"defaultValue": "uksouth",
"type": "String"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Logic/workflows",
"apiVersion": "2017-07-01",
"name": "[parameters('logic_app_name')]",
"location": "[parameters('location')]",
"properties": {
"state": "Enabled",
"definition": {
},
"parameters": {
}
}
}
],
"outputs": {
}
}
Logic app code view in Azure
The Logic app designer is viewable in code view (json):

Minimise both"definition" and "parameters" from Azure Portal and copy/paste into the .json above.

Your completed .json file will look similar to this:
- Notice it also includes
"$connections", to include connections to the likes of Blob storage.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"logic_app_name": {
"type": "String"
},
"location": {
"defaultValue": "uksouth",
"type": "String"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Logic/workflows",
"apiVersion": "2017-07-01",
"name": "[parameters('logic_app_name')]",
"location": "[parameters('location')]",
"properties": {
"state": "Enabled",
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {
"Create_blob_(V2)": {
"inputs": {
"body": "@utcNow()",
"headers": {
"ReadFileMetadataFromServer": true
},
"host": {
"connection": {
"name": "@parameters('$connections')['azureblob']['connectionId']"
}
},
"method": "post",
"path": "/v2/datasets/@{encodeURIComponent(encodeURIComponent('tamopslogicappsa'))}/files",
"queries": {
"folderPath": "/tamopslogicappsc",
"name": "thomas-blob-@{utcNow()}",
"queryParametersSingleEncoded": true
}
},
"runAfter": {},
"runtimeConfiguration": {
"contentTransfer": {
"transferMode": "Chunked"
}
},
"type": "ApiConnection"
}
},
"contentVersion": "1.0.0.0",
"parameters": {
"$connections": {
"defaultValue": {},
"type": "Object"
}
},
"triggers": {
"Recurrence": {
"evaluatedRecurrence": {
"frequency": "Day",
"interval": 1,
"schedule": {
"minutes": [
0
]
}
},
"recurrence": {
"frequency": "Day",
"interval": 1,
"schedule": {
"minutes": [
0
]
}
},
"type": "Recurrence"
}
}
},
"parameters": {
"$connections": {
"value": {
"azureblob": {
"connectionId": "/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamops-logicapps-rg/providers/Microsoft.Web/connections/azureblob",
"connectionName": "azureblob",
"connectionProperties": {
"authentication": {
"identity": "/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamops-logicapps-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/tamops-logicapp-mi",
"type": "ManagedServiceIdentity"
}
},
"id": "/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/providers/Microsoft.Web/locations/uksouth/managedApis/azureblob"
}
}
}
}
}
}
],
"outputs": {
}
}
I run the above in Terraform:
- Notice the reference to
parameters_content? You can automate more parameters if required. In this example, I have hard coded values. However, you can easily parameterise all the values. This allows you to deploy multiple variations of the same Logic App easily between environments etc
data "local_file" "logic_app" {
filename = "${path.module}/workflow.json"
}
resource "azurerm_resource_group_template_deployment" "logic_app_deployment" {
resource_group_name = azurerm_resource_group.tamopsrg.name
deployment_mode = "Incremental"
name = "logic-app-deployment"
template_content = data.local_file.logic_app.content
parameters_content = jsonencode({
"logic_app_name" = { value = azurerm_logic_app_workflow.tamops-logicapp.name }
"location" = { value = azurerm_logic_app_workflow.tamops-logicapp.location }
})
depends_on = [azurerm_logic_app_workflow.tamops-logicapp]
}
Running the above, we can view the deployment in Azure – its been successful:

Wrapping up
This method may seem unconventional at first. However, it offers a pragmatic balance. It combines the visual design capabilities of the Azure Portal and the automation powers of Terraform.
It’s particularly useful for complex Logic Apps. Visual design I believe is beneficial for these apps. However, we still want the benefits of Infrastructure as Code.
Remember, in the world of Azure and DevOps, there’s often more than one way to achieve a goal. Don’t be afraid to explore different approaches to find what works best for your specific use case.
Thanks a lot Thomas for sharing this, transforming logic to Terraform IaC is challenging. I am wondering is there a way to automate the connection creation to make the IaC dynamic? Have you found a way?
Hi Christine,
Could you provide some more context?
Thanks