Skip to content

Azure .NET 8 Isolated Function AppSettings Configuration

WARNING

This article addresses a critical breaking change when migrating Azure Functions from .NET 6 (in-process) to .NET 8 (isolated worker model). Key configuration behaviors change significantly in production environments.

Problem Statement

Azure Functions running on .NET 8 (isolated worker model) fail to load appsettings.json configurations when deployed to Azure, despite working locally. Configuration objects and nested properties defined in appsettings.json return null values in production environments, while environment variable overrides still function. The root cause is an incorrect base path resolution in Azure's Linux environment, leading to the configuration builder looking for appsettings.json in the wrong directory.

Core Symptoms

  • Configuration loads locally but fails in Azure PaaS environments
  • IOptions<T> properties return null values
  • Environment variable overrides still work (e.g., DummyOption__Foo)
  • Confirmed file existence in deployment directory

Use the assembly execution path to reliably locate appsettings.json across environments. This approach works for Linux/Windows and all hosting plans (Consumption/App Service):

1. Create Configuration Helper

cs
// Startup/ConfigHelper.cs
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace YourNamespace.Startup;

internal static class ConfigHelper
{
    public static void ConfigureAppConfig(
        HostBuilderContext context, 
        IConfigurationBuilder builder)
    {
        builder.SetBasePath(GetApplicationBasePath());
        builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
        
        // Environment-specific configuration
        builder.AddJsonFile(
            $"appsettings.{context.HostingEnvironment.EnvironmentName}.json", 
            optional: true);
            
        builder.AddEnvironmentVariables();

        if (context.HostingEnvironment.IsDevelopment())
        {
            builder.AddUserSecrets<Program>(optional: true);
        }
    }

    private static string GetApplicationBasePath()
    {
        var assemblyPath = Assembly.GetExecutingAssembly().Location;
        return Path.GetDirectoryName(assemblyPath) ?? Directory.GetCurrentDirectory();
    }
}

2. Program.cs Implementation

cs
// Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureAppConfiguration(ConfigHelper.ConfigureAppConfig)
    .ConfigureServices((context, services) =>
    {
        // Bind configuration
        services.Configure<DummyOption>(
            context.Configuration.GetSection(nameof(DummyOption)));
    })
    .Build();

host.Run();

3. Project File Configuration

xml
<!-- .csproj -->
<ItemGroup>
  <None Update="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
  <None Update="appsettings.Development.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <CopyToPublishDirectory>Never</CopyToPublishDirectory>
  </None>
</ItemGroup>

Why This Works

  • Assembly.GetExecutingAssembly().Location reliably locates the DLL directory
  • Not dependent on Azure hosting implementation details
  • Works for both Windows and Linux environments
  • Compatible with all hosting plans (Consumption/App Service)

Alternative Solution: Azure-Friendly Path Hardcoding

For Linux deployments where assembly approach isn't feasible, use conditional path logic:

cs
// Program.cs
builder.SetBasePath(true switch
{
    _ when context.HostingEnvironment.IsDevelopment() => Directory.GetCurrentDirectory(),
    _ when Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") != null 
        => "/home/site/wwwroot",
    _ => Directory.GetCurrentDirectory()
});

When to Use This Approach

  1. Projects not requiring cross-host flexibility
  2. Linux-specific deployments
  3. When Assembly methods are restricted

Path Reliability Warning

Hardcoded paths like /home/site/wwwroot may break in future Azure updates. Assembly-based solution is preferred for long-term stability.

Explanation of Failure

Azure Functions Isolated Worker has different runtime behaviors across environments:

Path SourceLocal ResultAzure ResultIssue
HostingEnvironment.ContentRootPathProject directory/azure-functions-hostHost runtime config
Directory.GetCurrentDirectory()Project directory/tmp/functions/standby/wwwrootLinux consumption temporary path
Assembly Location/bin/Debug/net8.0/home/site/wwwrootConsistent location

Environment Variables Take Precedence

Azure portal environment variables always override appsettings.json values. For nested objects, use object binding via configuration.GetSection() instead of flattening to environment variables.

Deployment Verification

Confirm file placement in Azure:

  1. Use Kudu Console or Azure App Service Editor
  2. Navigate to /home/site/wwwroot
  3. Verify appsettings.json exists alongside DLLs
  4. Check file contents with cat appsettings.json
bash
# Azure deployment file structure
/home/site/wwwroot/
├── YourFunctionApp.dll
├── host.json
├── appsettings.json
└── bin/

Key Takeaways

  1. Use Assembly location for reliable cross-platform configuration
  2. Never rely on HostingEnvironment.ContentRootPath in Azure
  3. Confirm Publish settings for appsettings.json
  4. Environment-specific files (e.g., appsettings.Production.json) are supported
  5. Local development uses local.settings.json automatically (don't deploy this)

Configuration loading for .NET 8 Azure Functions requires explicit path resolution as shown. The assembly-based approach provides the most reliable solution across all hosting environments.