An environment in ASP.NET Core is the context in which an application runs and allows changing its configuration and behavior depending on whether we are in development, testing, or production.
This allows us to adapt the application’s behavior: debug locally, test changes in staging, and apply stricter configuration in production. Because running an API on your local machine is one thing, but deploying it on a server with real users watching is quite another.
For example, in Development we can show detailed errors and enable Swagger. In Production, the norm is to hide internal details, enable HSTS, use real configuration, and log errors in a controlled manner.
Common Environments
ASP.NET Core allows using any environment name, but three names appear constantly:
- Development: local development environment with debugging tools and detailed error messages.
- Staging: testing environment similar to production, useful for validating deployments before going live.
- Production: real environment, optimized for security, performance, and stability.
If no environment is configured, ASP.NET Core assumes Production by default. This makes sense, as it’s preferable to start in safe mode rather than showing an error page with all internal details.
Environment Variables
The environment is typically set via an environment variable. The most common one in ASP.NET Core applications is ASPNETCORE_ENVIRONMENT.
$env:ASPNETCORE_ENVIRONMENT="Development"On Linux or macOS:
export ASPNETCORE_ENVIRONMENT=DevelopmentThere is also DOTNET_ENVIRONMENT, which applies to the .NET generic host. In modern projects using WebApplication.CreateBuilder, it’s best to not define both with different values, otherwise you’re asking for an entertaining afternoon of diagnosis.
Environment variable names are case-sensitive on Linux. ASPNETCORE_ENVIRONMENT and aspnetcore_environment are not the same.
launchSettings.json
When working locally, Visual Studio, Rider, or dotnet run can use the Properties/launchSettings.json file. This file is intended for local development and is not published with the application.
{
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Staging": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}
}
}
}We can run a specific profile from the CLI:
dotnet run --launch-profile StagingTo ignore local profiles and use what’s set in the console:
dotnet run --no-launch-profileEnvironment-specific appsettings
ASP.NET loads configuration files specific to the environment. The pattern is:
appsettings.{Environment}.jsonFor example:
appsettings.json
appsettings.Development.json
appsettings.Staging.json
appsettings.Production.jsonThe idea is simple: appsettings.json contains the base configuration, and the environment-specific file overrides the keys that need changing.
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=prod-sql;Database=MiApp;..."
}
}appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MiAppDev;..."
}
}This way, the same application can use more logging and a local database in development, but connect to the real configuration when running in production.
Priority Order
Configuration in ASP.NET Core comes from multiple sources. In the default setup, the usual order is:
appsettings.json.appsettings.{Environment}.json.- User Secrets (only in
Development). - Environment variables.
- Command-line arguments.
The key rule is: if the same key appears in multiple places, the source with higher priority wins.
That’s why an environment variable can override a value from appsettings.json, and a command-line argument can override almost everything before it.
For hierarchical keys in environment variables, use double underscore __ instead of colon :. For example, ConnectionStrings__DefaultConnection.
Changing Behavior by Environment
Sometimes changing configuration values is not enough. We might also want to enable or disable parts of the pipeline based on the environment.
A typical example is in Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.Run();In development, we show a detailed error page. In production, we use a safe handler and enable HSTS.
We can also check for custom environments:
if (app.Environment.IsEnvironment("Testing"))
{
// Specific configuration for automated tests
}Custom environments are valid. Just be consistent with the names and create the corresponding configuration files, like appsettings.Testing.json.
Setting the Environment in Production
In production, the environment is usually configured from the deployment platform.
In Azure App Service, we can set ASPNETCORE_ENVIRONMENT from the application settings:
- Go to the App Service.
- Navigate to Settings > Configuration > Application settings.
- Add
ASPNETCORE_ENVIRONMENTwith the valueProduction,Staging, or the appropriate one.
Azure restarts the application when these settings change, so the new environment applies on the next startup.
In Docker, we can define the environment in the Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_ENVIRONMENT=ProductionOr pass it when running the container:
docker run -e ASPNETCORE_ENVIRONMENT=Production my-appIf using Docker Compose:
services:
web:
image: my-app
environment:
- ASPNETCORE_ENVIRONMENT=ProductionBest Practices
Don’t store real secrets in appsettings.Production.json if that file goes into the repository. For development, use User Secrets. For production, use environment variables, Azure Key Vault, or another secret store.
Also, avoid cluttering the application with if (app.Environment.IsDevelopment()). It’s fine for simple pipeline differences. For larger differences, it’s usually better to use configuration or dependency injection.
For example, in development, we can register a FakeEmailSender, and in production, an SmtpEmailSender. The rest of the application doesn’t need to know where it’s running.