Managing infrastructure-as-code with Terraform (or any such equivalent tool) should provide a consistent, predictable experience, but when working with Azure Functions, I’ve encountered a frustrating inconsistency between different application technology stack implementations.

Our team develops microservices using Azure Functions in both languages, and while the .NET ones behave as expected (for the most part), the Python functions introduce unexpected configuration drift.

The issue arises post-deployment, making it invisible to ClickOps engineers working in the Azure portal but painfully obvious to anyone using Terraform or other IaC tools. This kind of silent churn leads to extra toil for automation-focused teams, forcing them to either manually mitigate drift or introduce workarounds that shouldn’t be necessary in the first place.

Unseen Environment Variable Changes

One of the biggest offenders is Azure Functions’ handling of environment variables. We’ve observed that after deploying a Python-based function, additional app settings appear that weren’t defined in Terraform.

Alt

A particularly frustrating example is the addition of settings like:

AzureWebJobs.quality_assessment_eventgrid_trigger.Disabled = 1  # Probably means 'true'
AzureWebJobs.quality_assessment_sbt.Disabled = 0  # Probably means 'false'

These settings were never explicitly declared, yet they show up post-deployment, creating drift. This behavior is absent in the .NET Azure Functions, which stick to what was defined in Terraform. ClickOps engineers working in the Azure portal won’t see these changes, but for those of us managing infrastructure-as-code, Terraform flags these unexpected mutations as drift, forcing us to decide between manually reconciling changes or implementing workarounds to suppress them.

The Mystery of sticky_settings

Another source of unnecessary churn is the sticky_settings property, which appears specifically in Python Azure Functions (maybe others too?). Unlike the environment variable issue, this setting introduces an entirely new property that doesn’t exist in the .NET version. What does it do? Why is it being added automatically? These are questions without clear answers, and yet Terraform dutifully detects the difference. Without any documentation or transparency about its purpose, we are left to assume its function and, ultimately, work around it.

Alt

To avoid constant Terraform plan drift, I resorted to this lifecycle configuration:

lifecycle {
  ignore_changes = [
    sticky_settings, # Python Application Insights Yuck
  ]
}

While this stops Terraform from detecting changes to sticky_settings, it’s an imperfect compromise. Ignoring properties means losing visibility and control over what gets modified, which is not an ideal situation for anyone managing infrastructure responsibly.

The Hidden Tags Problem

Another source of churn comes from additional hidden tags that get injected into the function app’s configuration. These tags, which seem to relate to Python’s Application Insights integration, appear without warning and without an equivalent in .NET-based Azure Functions. Unlike manually added tags that serve a clear purpose, these seem to be added automatically under the hood.

Alt

This one is particularly egregious as the App Insights Instrumentation Key is exposed. Isn’t this supposed to be treated as a secret? Yet, its drop unceremoniously into the tags where a savy operator can snag it. Can you say “Lateral Privilege Escalation Attack”?

Again, Terraform picks up on the difference, but ClickOps engineers won’t see it. The only way to remove the churn was to modify our Terraform configuration:

lifecycle {
  ignore_changes = [
    tags # Python Application Insights Yuck
  ]
}

While ignoring tags is slightly more tolerable than ignoring core settings, this still feels like unnecessary toil. When managing infrastructure declaratively, we expect changes to be intentional and transparent — not dictated by hidden, undocumented processes within Azure’s Python runtime.

Conclusion

The inconsistency between .NET and Python Azure Functions creates an unnecessary burden for those managing infrastructure-as-code. Environment variables appearing out of nowhere, unexplained properties like sticky_settings, and hidden tags all introduce churn that Terraform can see but ClickOps engineers cannot.

These inconsistencies erode trust in automation tools and force engineers to implement workarounds that should never be necessary. Azure’s function runtimes need to be held to the same standard across languages, ensuring that infrastructure behaves predictably and transparently, regardless of whether you’re writing in C# or Python.

Until that happens, those of us managing infrastructure the right way are left cleaning up the mess.