If you’ve ever set:
ASPNETCORE_ENVIRONMENT=Production
…only to find your app still thinks it’s running in Development — welcome to the club.
This has caused years of confusion in the .NET ecosystem, especially since .NET Core evolved into the “generic host” world.
Let’s break it down properly.
The Symptom
You write something like:
var runtimeEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");var isProductionEnvironment = string.Equals(
runtimeEnvironment,
"Production",
StringComparison.OrdinalIgnoreCase
);
You set:
"ASPNETCORE_ENVIRONMENT": "Production"
And…
isProductionEnvironment == false
🤯
The Root Cause
There are two environment variables in play:
| Variable | Used By | Typical App Type |
|---|---|---|
ASPNETCORE_ENVIRONMENT | Web Host | ASP.NET Core Web Apps |
DOTNET_ENVIRONMENT | Generic Host | Worker services, Console apps, modern .NET hosting |
The problem?
.NET Core evolved.
Early ASP.NET Core used ASPNETCORE_ENVIRONMENT.
Later, when Microsoft introduced the Generic Host (Host.CreateDefaultBuilder()), they introduced DOTNET_ENVIRONMENT.
Now we live in a world where:
- Some parts of the runtime check
ASPNETCORE_ENVIRONMENT - Some check
DOTNET_ENVIRONMENT - Some prioritise one over the other
- And your own code might be reading the wrong one entirely
Why This Is So Frustrating
- Both variables can exist at the same time.
- One can override the other.
- VS Code
tasks.jsonandlaunch.jsonhandle env vars differently. dotnet watchcan behave differently thandotnet run.- IIS ignores both and uses its own configuration.
- Azure App Service sets its own environment values.
It’s the perfect storm of “it works on my machine.”
What Actually Happens Under The Hood
If you’re using:
var builder = WebApplication.CreateBuilder(args);
The environment name ultimately comes from:
IHostEnvironment.EnvironmentName
That value is populated by:
DOTNET_ENVIRONMENT- Fallback to
ASPNETCORE_ENVIRONMENT - Default =
"Production"
So if your code manually reads:
Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
…then setting only ASPNETCORE_ENVIRONMENT will do absolutely nothing.
The Clean Way To Do It
Instead of manually reading environment variables:
❌ Don’t do this:
var env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
✅ Do this:
var isProduction = app.Environment.IsProduction();
Or:
var envName = app.Environment.EnvironmentName;
This removes all ambiguity.
The Practical Fix (When You’re Sick of It Not Working)
In VS Code tasks.json, just set both:
"env": {
"ASPNETCORE_ENVIRONMENT": "Production",
"DOTNET_ENVIRONMENT": "Production"
}
Problem solved.
Why Microsoft Didn’t Just Pick One
Historical evolution.
- ASP.NET Core 1.x →
ASPNETCORE_ENVIRONMENT - .NET Core Generic Host →
DOTNET_ENVIRONMENT - Backward compatibility had to be preserved
- So now both exist
It’s not a bug.
It’s legacy design inertia.
Real Production Advice
If you’re deploying properly:
- IIS sets environment in
web.config - Azure sets environment in Application Settings
- Docker sets environment in container config
- Windows Services use system environment variables
The confusion mainly happens in:
- Local dev
- VS Code
- dotnet watch
- Manual CLI runs
The Mental Model That Prevents Future Pain
Think of it this way:
ASPNETCORE_ENVIRONMENT is the old web host world
DOTNET_ENVIRONMENT is the generic host world
Modern apps use the generic host.
So if in doubt — set both.
Final Takeaway
If your Production check is false and you’re sure it shouldn’t be:
- Print both environment variables.
- Check
app.Environment.EnvironmentName. - Stop manually reading env vars.
- Use the framework helpers.
And if you’re debugging in VS Code?
Check launch.json before you lose your sanity.
Leave a Reply