ASP.NET Core and Docker Environment Variables

Scott Brady
Scott Brady
Docker ・ Updated March 2023
Docker Logo

When working with ASP.NET Core and Docker, it can be easy to get confused when trying to figure out who is setting what configuration variable and how. Especially if you are using one of the Visual Studio templates with Docker support.

In this article, you’re going to see how configuration settings are applied in both ASP.NET Core, and Docker, and how they interoperate.

ASP.NET Core configuration

Let’s start with a crash course on the basics of ASP.NET Core configuration handling.

Configuration Builder

In a typical ASP.NET Core application, your default configuration will be doing something similar to the following:

var configuration = new ConfigurationBuilder()
  .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
  .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
  .AddEnvironmentVariables()
  .Build();

This used to be very clear in your Startup or Program class, however, it is now hidden behind calls such as WebHost.CreateDefaultBuilder and WebApplication.CreateBuilder.

This configuration of IConfigurationBuilder is where external configuration settings meet ASP.NET Core. This is similar to the web.config and ConfigurationManager classes you may have used before when using the .NET Framework (ASP.NET 4.x), but instead of using IIS and config transforms, you are instead using multiple sources to build up your configuration.

ConfigurationBuilder uses a builder pattern and you can create your own extensions to support your configuration store of choice (maybe Azure Key Vault or Table Storage), but these AddJsonFile and AddEnvironmentVariables builder methods are the most common and are configured out-of-the-box.

The appsettings.json file is the replacement for the appSettings element within web.config, while the environment variables are the environment variables on the hosting platform. For example in IIS you might have: USERNAME, APP_POOL_ID, and SystemDrive. On my old Windows 10 machine using IIS Express, I counted a total of 71 environment variables.

As each method builds up your configuration, any duplicate keys will get overwritten. So, if each configuration store has a variable called Duplicate, you’d see the following:

var configuration = new ConfigurationBuilder()
  .AddJsonFile("appsettings.json") // "key1" set to "json1"
  .AddJsonFile($"appsettings.{env.EnvironmentName}.json") // "key1" overwritten to "json2"
  .AddEnvironmentVariables() // "key1" overwritten to "environment"
  .Build();

Where the final value is "environment". So, start with your generic default settings and then override them with your platform/environment specifics.

Once built you receive a basic key value store of IConfigurationRoot which can also be used as IConfiguration.

IOptions

This configuration abstraction then hooks into the IOptions abstraction, which uses your configuration (or specific parts of it) to populate objects that represent your configuration settings. No more self-implemented logic just to pull out settings from the web.config!

You might see this in action like so:

services.AddOptions();
services.Configure<EmailConfiguration>(configuration);

Where EmailConfiguration just contains some properties with getters and setters. This can then later be retrieved as:

public EmailService(IOptions<EmailConfiguration> options) {
    EmailConfiguration config = options.Value;
}

You could access everything directly by key through IConfiguration, but this approach is a lot more readable and maintainable.

Docker Environment Variables

Docker has support for environment variables to be set in a variety of different ways, and these Docker environment variables are loaded automatically into our ASP.NET Core application as part of the AddEnvironmentVariables method.

Dockerfile

Docker allows environment variables to be configured in your Dockerfile using the ENV instruction. This instruction takes a key value pair, separated by a space or an equals character. For example:

ENV EmailServer=127.0.0.1

Be aware that any environment variables set in the image will be exposed to anyone else with access to the image. Therefore, you must not store sensitive configuration, such as connection strings and credentials, in your Docker images.

See the Docker reference documentation for more details.

Docker Run

You can override the environment variables set in the Dockerfile when running a container by using the --env or -e option:

docker container run -e EmailServer=127.0.0.1 <image_name>

You can use the -e option multiple times to set multiple environment variables.

Another alternative is to load the environment variables from a file using the --env-file option. Both approaches work for the docker run alias.

Docker Compose

Docker Compose also supports environment variables to be set as part of a container’s configuration in the Docker compose file.

version: "3.9"
services:
  web:
    build: .
    ports:
      - "8000:5000"
    environment:
      - EmailServer=127.0.0.1

I recommend reading the Docker documentation to learn more about the order of precedence for Docker’s environment variables.

Docker launchSettings.json profile

You can also configure Visual Studio to run your Docker container by updating your launchSettings.json file with a new profile.

This will need your project to have a reference to the Microsoft.VisualStudio.Azure.Containers.Tools.Targets NuGet package.

dotnet add package Microsoft.VisualStudio.Azure.Containers.Tools.Targets

Your new profile in launchSettings.json will need to use the commandName of Docker and set your environment variables either using the environmentVariables section or by passing them in as DockerfileRunArguments (see the Docker File section above).

{
  "profiles": {
    "docker": {
      "commandName": "Docker",
      "httpPort": 5000,
      "environmentVariables": {
        "EmailServer": "127.0.0.1"
      }
    },
    // launch other profiles
  }
}

To learn more about configuring this profile, check out the Visual Studio documentation.

Managing environment variables in ASP.NET Core

In ASP.NET Core, you can see what environment you are running in by injecting IHostEnvironment and using the EnvironmentName property (e.g. staging, sandbox, NFT, production, or whatever fun name your organization chooses). Useful extension methods include IsEnvironment(environmentName) and IsDevelopment, IsStaging, and IsProduction.

You can configure the environment name using the ASPNETCORE_ENVIRONMENT environment variable, which you can also set in your Dockerfile, Docker CLI, or Docker Compose file. If this environment variable is not present, it sensibly defaults to “Production”.

A common approach is to use different functionality or default settings based on the environment you are running in rather than manually setting them each time.

In older versions of ASP.NET Core, IHostEnvironment was called IHostingEnvironment and the ASPNETCORE_ENVIRONMENT environment variable was ASPNETCORE_ENV.

ASP.NET Core JSON configuration and accessing hierarchical configuration

You can also handle configuration sections used in JSON files, such as the default appsettings.json file you saw previously. While these settings are usually overwritten by environment variables, it is a good idea to set some defaults.

{
  "EmailServer": "127.0.0.1"
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Hierarchical configuration, like the logging section above, is super useful for encapsulating related configuration options. To access a setting individually, as you have throughout the rest of this article, you’ll need to use a colon delimiter. For example, Logging:LogLevel:Default.

Check out the Microsoft documentation to learn more about the options pattern and configuration binding in .NET.