Generally available: Enhanced Cost Management exports
April 28, 2025Migração de Aplicações Full Framework para Azure AKS e .NET Core
April 29, 2025By using Logic Apps in Azure, we can automate and orchestrate workflows and integrations with many systems using low-code/no-code. These can be deployed in consumption (server-less) or standard mode. The standard mode Logic App service also allows us to use security-minded features such as virtual networks and attached firewall rules, private endpoints and more. These services work well in an enterprise deployment with high security requirements.
However – the standard mode uses a provisioned app service and server farm and behaves quite differently when deploying resources.
It took us a lot of time and research to find a stable way to deploy workflows and, especially, workflow connections. We thought it would be good to share our findings with the community.
Since standard Logic App services uses a file system to store definitions, we can use IAC (Terraform) to update the files correctly.
In this blog, we’ll walk through a practical approach to deploying workflows in a Standard Logic App using Terraform. The Standard model introduces more flexibility and control—but also comes with a few deployment challenges when working with Infrastructure as Code. Our focus will be on how to set up everything cleanly, including directory creation, workflow file management, and authorization setup using a User Assigned Managed Identity.
Challenge:
There is no attribute in the Terraform azurerm_logic_app_standard resource to define or create workflows directly. As a result, workflows typically need to be manually created via the Azure Portal, which breaks infrastructure automation and version control best practices.
Solution:
To overcome this limitation, we use a fully automated approach that lets us manage workflows using Terraform:
Use a User Assigned Managed Identity for secure, password-less access to other Azure services.
Create a directory inside the Logic App’s file share under the path site/wwwroot/ — this acts as the container for the workflow.
Inside this folder, create a file named workflow.json, which contains the workflow definition. This filename is required and must not be changed.
Upload the workflow.json file via Terraform into the correct directory in the file share.
The workflow JSON includes the authorization setup, where the User Assigned Identity is injected dynamically from the main.tf file.
Template:
This Terraform configuration sets up the necessary Azure infrastructure to host Logic App Standard workflows that are stored in an Azure File Share and configured dynamically using JSON templates.
Deploy the Logic App Service
#Retrieves the current Azure client and subscription configuration if required.
data “azurerm_client_config” “example” {}
#Creates a resource group
resource “azurerm_resource_group” “example” {
name = “example_rg”
location = “West Europe”
}
#Creates a storage account and a file share
resource “azurerm_storage_account” “logicapp” {
name = “datadiodestra”
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = “Standard”
account_replication_type = “LRS”
}
resource “azurerm_storage_share” “example” {
name = “sharename”
storage_account_id = azurerm_storage_account.logicapp.id
quota = 50
}
#Creates a App Service Plan for hosting Logic Apps.
resource “azurerm_service_plan” “example” {
name = “datadiode_plan”
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
os_type = “Windows”
sku_name = “WS2”
}
#Deploys a user-assigned managed identity for secure resource access.
resource “azurerm_user_assigned_identity” “identity” {
location = azurerm_resource_group.example.location
name = “milogicapp”
resource_group_name = azurerm_resource_group.example.name
}
#Deploys a Logic App Standard with connected identity, storage, and app settings.
resource “azurerm_logic_app_standard” “example” {
name = “onboarding-logicapp-52030-tests”
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
app_service_plan_id = azurerm_service_plan.example.id
storage_account_name = azurerm_storage_account.logicapp.name
storage_account_access_key = azurerm_storage_account.logicapp.primary_access_key
storage_account_share_name = azure_storage_share.example.name
identity {
type = “UserAssigned”
identity_ids = [azurerm_user_assigned_identity.identity.id]
}
app_settings = {
“FUNCTIONS_WORKER_RUNTIME” = “node”
“WEBSITE_NODE_DEFAULT_VERSION” = “~18”
}
}
Creating Workflows
Creating a designed workflow directly in the Azure Portal is straightforward. However, when source control or deploying a templated workflow using CI/CD and pipelines is required, the Visual Studio Code plugin can be used to deploy the templates. Yet, this approach is not ideal for automated pipelines.
To address this, we now utilize a storage account with a file share where workflows are stored. Simply create a folder and upload the .JSON file to activate the workflow.
# Reference existing storage file share
data “azurerm_storage_share” “existing_file_share” {
name = azure_storage_share.example.name
storage_account_name = azurerm_storage_account.logicapp.name
}
# Create directories inside the file share
resource “azurerm_storage_share_directory” “onboarding_folder” {
name = “site/wwwroot/onboarding”
storage_share_id = data.azurerm_storage_share.existing_file_share.id
}
resource “azurerm_storage_share_directory” “offboarding_folder” {
name = “site/wwwroot/offboarding”
storage_share_id = data.azurerm_storage_share.existing_file_share.id
}
# Renders onboarding and offboarding workflow JSON templates and saves them locally.
resource “local_file” “onboarding_json_file” {
content = data.template_file.onboarding_json.rendered
filename = “${path.module}/temp_onboarding.json”
}
resource “local_file” “offboarding_json_file” {
content = data.template_file.offboarding_json.rendered
filename = “${path.module}/temp_offboarding.json”
}
# Upload rendered JSON workflows to respective directories
resource “azurerm_storage_share_file” “onboarding_workflow” {
name = “workflow.json”
storage_share_id = data.azurerm_storage_share.existing_file_share.id
path = azurerm_storage_share_directory.onboarding_folder.name
source = local_file.onboarding_json_file.filename
content_type = “application/json”
depends_on = [azurerm_storage_share_directory.onboarding_folder, local_file.onboarding_json_file]
}
resource “azurerm_storage_share_file” “offboarding_workflows” {
name = “workflow.json”
storage_share_id = data.azurerm_storage_share.existing_file_share.id
path = azurerm_storage_share_directory.offboarding_folder.name
source = local_file.offboarding_json_file.filename
content_type = “application/json”
depends_on = [azurerm_storage_share_directory.offboarding_folder, local_file.offboarding_json_file]
}
# Define variables for JSON templates
data “template_file” “onboarding_json” {
template = file(“${path.module}/workflow/onboarding.json”)
vars = {
subscription_id = data.azurerm_client_config.example.subscription_id
resource_group_name = azurerm_resource_group.example.name
UserAssigned_mi = azurerm_user_assigned_identity.identity.name
}
}
data “template_file” “offboarding_json” {
template = file(“${path.module}/workflow/offboarding.json”)
vars = {
subscription_id = data.azurerm_client_config.example.subscription_id
resource_group_name = azurerm_resource_group.example.name
UserAssigned_mi = azurerm_user_assigned_identity.identity.name
}
}
# Upload rendered JSON workflows to respective directories
resource “azurerm_storage_share_file” “onboarding_workflow” {
name = “workflow.json”
storage_share_id = data.azurerm_storage_share.existing_file_share.id
path = azurerm_storage_share_directory.onboarding_folder.name
source = local_file.onboarding_json_file.filename
content_type = “application/json”
depends_on = [azurerm_storage_share_directory.onboarding_folder, local_file.onboarding_json_file]
}
resource “azurerm_storage_share_file” “offboarding_workflows” {
name = “workflow.json”
storage_share_id = data.azurerm_storage_share.existing_file_share.id
path = azurerm_storage_share_directory.offboarding_folder.name
source = local_file.offboarding_json_file.filename
content_type = “application/json”
depends_on = [azurerm_storage_share_directory.offboarding_folder, local_file.offboarding_json_file]
}
Note: The JSON template reference serves as example here to demonstrate how dynamic values can be injected from Terraform code into JSON files.