This error occurs when Terraform tries to create an Azure Function App but cannot find or access the specified storage account. The issue typically stems from resource creation timing, missing dependencies, firewall rules, or incorrect storage account references in your Terraform configuration.
When deploying an Azure Function App with Terraform, the error 'StorageAccountNotFound' indicates that the Azure ARM API cannot locate or access the storage account that the Function App needs for its runtime storage. Azure Function Apps require a storage account for several critical functions: storing application code, managing function executions, maintaining state, and handling logs. This storage account must exist and be accessible to the Function App when it's created. The error occurs when Terraform attempts to provision the Function App but the storage account is either not yet created, not accessible due to network restrictions, or the reference in your configuration is incorrect. This is a common issue in infrastructure-as-code deployments because Terraform manages multiple resources in parallel, and the Function App may try to reference the storage account before it's fully provisioned.
The most common cause is a race condition where Terraform tries to create the Function App before the storage account is fully provisioned. Add an explicit depends_on block to ensure proper ordering:
resource "azurerm_storage_account" "function_storage" {
name = "stfunction12345678"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_linux_function_app" "example" {
name = "example-function-app"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id
storage_account_name = azurerm_storage_account.function_storage.name
storage_account_access_key = azurerm_storage_account.function_storage.primary_access_key
# Explicit dependency to ensure storage account is fully provisioned first
depends_on = [azurerm_storage_account.function_storage]
}After adding the depends_on block, run terraform apply again. This ensures Terraform waits for the storage account to complete before attempting to create the Function App.
Ensure you're referencing the correct storage account name and that your configuration references the actual provisioned account:
# Correct: Using output of resource
resource "azurerm_linux_function_app" "example" {
storage_account_name = azurerm_storage_account.function_storage.name
storage_account_access_key = azurerm_storage_account.function_storage.primary_access_key
}
# Incorrect: Hardcoded name that may not exist
resource "azurerm_linux_function_app" "example" {
storage_account_name = "mystorageaccount"
storage_account_access_key = "hardcoded_key"
}Check your Terraform plan output to verify the correct storage account values are being used:
terraform plan | grep storage_accountIf you see incorrect values, update your configuration to reference the actual resource outputs.
If the storage account has firewall rules or is behind a VNET, the Function App may not be able to access it. Verify the storage account firewall settings:
resource "azurerm_storage_account" "function_storage" {
# ... other config ...
# For basic testing, allow all networks
default_action = "Allow"
bypass = ["AzureServices"]
}If you need network restrictions, configure them after the Function App is running:
resource "azurerm_storage_account_network_rules" "function_storage" {
storage_account_id = azurerm_storage_account.function_storage.id
default_action = "Deny"
ip_rules = [var.allowed_ip_addresses]
virtual_network_subnet_ids = [azurerm_subnet.function_subnet.id]
bypass = ["AzureServices"]
}For Function Apps in App Service Environments (ASE) or with VNET integration, ensure proper subnet configuration:
resource "azurerm_app_service_virtual_network_swift_connection" "example" {
app_service_id = azurerm_linux_function_app.example.id
subnet_id = azurerm_subnet.function_subnet.id
}Then apply the firewall rules after the integration is established.
A more secure approach is to use managed identity to authenticate between the Function App and storage account, avoiding the need for hardcoded access keys:
resource "azurerm_linux_function_app" "example" {
name = "example-function-app"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id
storage_uses_managed_identity = true
identity {
type = "SystemAssigned"
}
depends_on = [azurerm_storage_account.function_storage]
}
# Grant the Function App permission to access the storage account
resource "azurerm_role_assignment" "storage_blob_data_owner" {
scope = azurerm_storage_account.function_storage.id
role_definition_name = "Storage Blob Data Owner"
principal_id = azurerm_linux_function_app.example.identity[0].principal_id
depends_on = [azurerm_linux_function_app.example]
}
resource "azurerm_role_assignment" "storage_queue_data_contributor" {
scope = azurerm_storage_account.function_storage.id
role_definition_name = "Storage Queue Data Contributor"
principal_id = azurerm_linux_function_app.example.identity[0].principal_id
depends_on = [azurerm_linux_function_app.example]
}This approach is more secure and avoids issues with access key rotation.
If your storage account uses private endpoints or has network restrictions, add these required app settings to the Function App:
resource "azurerm_linux_function_app" "example" {
name = "example-function-app"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id
storage_account_name = azurerm_storage_account.function_storage.name
storage_account_access_key = azurerm_storage_account.function_storage.primary_access_key
app_settings = {
"AzureWebJobsStorage" = azurerm_storage_account.function_storage.primary_connection_string
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.function_storage.primary_connection_string
"WEBSITE_CONTENTSHARE" = "function-content-share"
"WEBSITE_VNET_ROUTE_ALL" = "1"
"WEBSITE_CONTENTOVERVNET" = "1"
"WEBSITE_DNS_SERVER" = "168.63.129.16"
}
depends_on = [azurerm_storage_account.function_storage]
}These settings ensure the Function App can communicate with the storage account through VNET integration and private endpoints.
Ensure the storage account is in the same resource group as the Function App, or explicitly reference the correct resource group:
# If storage account is in a different resource group
data "azurerm_storage_account" "existing" {
name = "existing-storage-account"
resource_group_name = "different-resource-group"
}
resource "azurerm_linux_function_app" "example" {
name = "example-function-app"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
service_plan_id = azurerm_service_plan.example.id
storage_account_name = data.azurerm_storage_account.existing.name
storage_account_access_key = data.azurerm_storage_account.existing.primary_access_key
}Verify the storage account exists by checking your Terraform state:
terraform state show azurerm_storage_account.function_storageIf using a data source, verify Azure permissions allow access to storage accounts in other resource groups.
### Understanding Azure Function App Storage Requirements
Azure Function Apps use storage accounts for three primary purposes:
1. Runtime Storage: Stores function execution metadata and bindings state
2. File Share: Hosts the application code and configuration (referenced via WEBSITE_CONTENTSHARE)
3. Queue/Blob Storage: Used by runtime triggers (queue triggers, blob triggers, etc.)
The storage account must be accessible at all times, not just during initial deployment.
### Storage Account Naming Restrictions
Azure storage account names have strict requirements:
- Must be between 3 and 24 characters
- Only lowercase letters and numbers
- Must be globally unique
Avoid naming conflicts by including a unique identifier or environment suffix. For example: stfuncprod123 or stfuncdev456.
### Race Conditions and Terraform Parallelism
By default, Terraform creates resources in parallel when possible. For Function Apps that depend on storage accounts, explicit depends_on is essential because Terraform cannot automatically infer the dependency from the resource references in newer azurerm provider versions.
If you continue to see race conditions, you can also disable parallelism:
terraform apply -parallelism=1However, explicit dependencies are the preferred solution.
### Troubleshooting with Azure CLI
If Terraform claims the storage account doesn't exist but you can see it in Azure Portal:
# List all storage accounts
az storage account list --resource-group <rg-name>
# Check if storage account is accessible
az storage account show --resource-group <rg-name> --name <storage-name>
# Verify Function App can access it
az functionapp config appsettings list --resource-group <rg-name> --name <function-app-name>This helps identify whether the issue is with your Terraform configuration or with actual Azure permissions/resources.
Error: Error installing helm release: cannot re-use a name that is still in use
How to fix "release name in use" error in Terraform with Helm
Error: Error creating GKE Cluster: BadRequest
BadRequest error creating GKE cluster in Terraform
Error: External program failed to produce valid JSON
External program failed to produce valid JSON
Error: Unsupported argument in child module call
How to fix "Unsupported argument in child module call" in Terraform
Error: network is unreachable
How to fix "network is unreachable" in Terraform