ASP.NET CORE MVC - Preparing for Production
Preparing the MVC Application for Production
Separate development and production configuration, keep secrets out of source-controlled files, review HTTPS and database concerns, publish a Release build, and verify the exact files that will be deployed in Part 20.
By the end of Part 19, learners will understand how a development configuration differs from production, how ASP.NET Core selects environment-specific configuration, how environment variables can override settings, and how to produce a deployment-ready application directory with dotnet publish.
1. Verify the Part 18 Solution
Open the Xubuntu terminal:
cd ~/aspnet-mvc-tutorial
dotnet build ProductManagementTutorial.sln
dotnet test ProductManagementTutorial.sln
Continue only when the application builds and the automated tests pass.
2. Development and Production Are Different
| Development | Production |
|---|---|
| Developer-friendly diagnostics | Safe, generic error responses |
| Local development database | Protected persistent database location |
| Development configuration | Production configuration |
| Local testing credentials | Server-managed secrets/configuration |
dotnet run | Published application hosted as a service |
3. Understand ASP.NET Core Environments
ASP.NET Core commonly uses environment names such as:
Development
Staging
Production
The environment affects configuration and application behaviour. Your existing Program.cs already checks:
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
Therefore development can use developer-friendly behaviour while non-development environments use the generic production exception handler and HSTS.
4. Inspect the Current Environment Files
Open the application folder:
cd ~/aspnet-mvc-tutorial/ProductManagement
ls appsettings*.json
You should already have:
appsettings.json
appsettings.Development.json
ASP.NET Core can also load appsettings.Production.json automatically when the active environment is Production. citeturn968371search0
5. Create appsettings.Production.json
Create the file:
touch appsettings.Production.json
code appsettings.Production.json
Add:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
}
}
This example changes production logging without storing secrets in the file.
Configuration files are easy to copy, publish or commit. Production secrets should come from a protected configuration source such as environment variables or an appropriate secret store. Microsoft specifically documents environment variables as a way to avoid storing secrets in source/local configuration files. citeturn968371search2
6. Understand Configuration Precedence
For this tutorial, think of the important configuration flow as:
Environment variables can override values supplied by JSON configuration. citeturn968371search2
7. Review appsettings.json
Open:
code appsettings.json
Your file currently contains settings such as:
"ConnectionStrings": {
"DefaultConnection": "Data Source=ProductManagement.db"
},
"AdminUser": {
"Email": "admin@example.com"
}
The administrator email is configuration rather than a password, but the connection string may eventually contain sensitive credentials if the application moves to a server database.
8. Override Nested Configuration with Environment Variables
ASP.NET Core uses double underscores in environment-variable names to represent nested configuration keys.
For example:
export ConnectionStrings__DefaultConnection="Data Source=/var/lib/productmanagement/ProductManagement.db"
export AdminUser__Email="admin@example.com"
These correspond to:
ConnectionStrings:DefaultConnection
AdminUser:Email
9. Set the ASP.NET Core Environment
For a shell session, set:
export ASPNETCORE_ENVIRONMENT=Production
Verify:
echo $ASPNETCORE_ENVIRONMENT
The result should be:
Production
Properties/launchSettings.json is a local development aid and isn't the mechanism you should depend on for configuring a deployed Linux service. Production deployment will set environment values outside the application project. Microsoft notes that launchSettings.json is used only on the local machine. citeturn968371search5
10. Review Production Error Handling
Open:
code Program.cs
Verify that this remains present:
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
Do not replace the production exception handler with detailed exception pages.
11. Review HTTPS and HSTS
Still in Program.cs, verify:
app.UseHttpsRedirection();
and the production block contains:
app.UseHsts();
Microsoft recommends HTTPS redirection and HSTS for production ASP.NET Core web applications, while noting that a reverse proxy may also handle these responsibilities. citeturn968371search5
12. Understand Reverse-Proxy Deployment
Part 20 will deploy the Linux application behind a reverse proxy.
The reverse proxy can terminate public HTTPS while Kestrel serves the application on an internal local port.
13. Review SQLite for Production
The tutorial application currently uses:
"Data Source=ProductManagement.db"
This is a relative path. Its exact location depends on the application's working directory.
For deployment, prefer an explicit writable data path such as:
Data Source=/var/lib/productmanagement/ProductManagement.db
This separates persistent data from published application binaries.
The SQLite database contains persistent application data. Production operation requires deliberate file ownership, permissions and backup procedures.
14. Review File Permissions
The process running ASP.NET Core needs permission to read its application files and read/write the SQLite database.
Conceptually:
The actual Linux user and permission commands will be configured in Part 20.
15. Do Not Deploy Development Secrets
Before publishing, search the source tree for obvious accidental secrets:
cd ~/aspnet-mvc-tutorial/ProductManagement
grep -RniE "password|secret|apikey|api_key|token" --exclude-dir=bin --exclude-dir=obj .
Review matches manually. Words such as Password may legitimately appear in Identity-related code or generated assets, so this is an audit aid rather than proof of a secret leak.
16. Build in Release Configuration
From the application folder:
dotnet build -c Release
Release builds are intended for deployment rather than interactive development.
17. Publish the Application
Remove any previous tutorial publish directory:
rm -rf ./publish
Then publish:
dotnet publish -c Release -o ./publish
dotnet publish compiles the application, processes its dependencies and writes the deployment assets to the chosen output directory. citeturn968371search9
18. Inspect the Published Output
ls -lah ./publish
find ./publish -maxdepth 1 -type f | sort
You should see files including the application assembly, dependency metadata, configuration files and static assets.
Typical important items include:
ProductManagement.dll
ProductManagement.deps.json
ProductManagement.runtimeconfig.json
appsettings.json
appsettings.Production.json
wwwroot/
19. Notice What dotnet publish Does Not Do
Publishing does not automatically:
- configure a Linux service;
- install Nginx;
- create a public domain;
- issue a TLS certificate;
- choose secure filesystem permissions;
- back up the database; or
- apply production environment variables.
Those are deployment/operations tasks handled in Part 20.
20. Test the Published Application Locally
Stop any existing dotnet run process. Then:
cd ~/aspnet-mvc-tutorial/ProductManagement/publish
export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__DefaultConnection="Data Source=/home/xubuntu/aspnet-mvc-tutorial/ProductManagement/ProductManagement.db"
dotnet ProductManagement.dll
This launches the published application rather than the source project.
The published folder is temporary deployment output. Using an explicit path avoids accidentally creating a second empty SQLite database simply because the working directory changed.
21. Verify Production Environment Behaviour
With the published application running, verify:
- the Products page loads;
- Identity login works;
- the Dashboard works after login;
- the Admin page remains role-protected;
- a nonexistent route displays the friendly status page; and
- the application is not showing detailed exception information publicly.
22. Verify Configuration from the Shell
In another terminal:
echo "$ASPNETCORE_ENVIRONMENT"
echo "$ConnectionStrings__DefaultConnection"
Remember that exported variables are scoped to the shell/process environment unless configured persistently by the operating system or service manager.
23. Re-run Automated Tests Before Deployment
Return to the tutorial root:
cd ~/aspnet-mvc-tutorial
dotnet test ProductManagementTutorial.sln -c Release
A production candidate should not be deployed knowingly with failing automated tests.
24. Production Readiness Checklist
| Area | Check |
|---|---|
| Build | Release build succeeds. |
| Tests | Automated tests pass. |
| Environment | Production configured outside local launch settings. |
| Secrets | No passwords/tokens stored in committed configuration. |
| Errors | Production exception handler enabled. |
| HTTPS | HTTPS/reverse-proxy plan defined. |
| Database | Persistent writable SQLite path planned. |
| Permissions | Application/data ownership plan defined. |
| Publishing | dotnet publish -c Release succeeds. |
| Backup | Database backup procedure planned. |
25. No EF Core Migration Is Required
Part 19 changes configuration and creates publish output. It does not alter the EF Core model, so no new migration is required.
26. Troubleshooting
The published application creates an empty database
A relative SQLite path is resolved from the application's working directory. Use an explicit database path through ConnectionStrings__DefaultConnection.
appsettings.Production.json seems ignored
Verify ASPNETCORE_ENVIRONMENT=Production is set for the process before the application starts.
Production environment variable is set but old app still behaves as Development
Environment variables are read when the process starts. Stop and restart the application after changing them.
dotnet publish succeeds but the website is not publicly accessible
Publishing only produces deployment files. Public hosting, service management, firewall/reverse proxy and TLS are Part 20 tasks.
27. Hands-On Exercise
- Create
appsettings.Production.json. - Set the environment to Production.
- Override the SQLite connection string with an environment variable.
- Build in Release.
- Publish to
./publish. - Inspect the output.
- Run
ProductManagement.dllfrom the publish folder. - Verify that it uses the intended database.
- Run the Release test suite.
28. Knowledge Check
- What is
ASPNETCORE_ENVIRONMENT? - What is
appsettings.Production.json? - Why should production secrets not be stored in source-controlled JSON?
- How are nested configuration keys represented as environment variables?
- What does
dotnet publishproduce? - Why can a relative SQLite path be risky during deployment?
- Why keep persistent data outside the publish directory?
- What is the purpose of HSTS?
- What is a reverse proxy?
- Why does Part 19 require no migration?
29. Part 19 Summary
- distinguished Development and Production environments;
- created
appsettings.Production.json; - used environment variables to override nested configuration;
- reviewed safe production secret handling;
- reviewed production exception handling, HTTPS and HSTS;
- planned a persistent SQLite data location;
- built the application in Release configuration;
- published the application with
dotnet publish; - tested the published DLL locally; and
- created a production-readiness checklist.
Appendix — Full Code and Commands for Final Verification
Appendix A — appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
}
}
Appendix B — Program.cs Production Section
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute(
"/Home/Status",
"?code={0}");
app.UseHttpsRedirection();
Appendix C — Production Environment Example
export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__DefaultConnection="Data Source=/var/lib/productmanagement/ProductManagement.db"
export AdminUser__Email="admin@example.com"
Appendix D — Release Build and Publish
cd ~/aspnet-mvc-tutorial/ProductManagement
dotnet build -c Release
rm -rf ./publish
dotnet publish -c Release -o ./publish
ls -lah ./publish
Appendix E — Published Application Test
cd ~/aspnet-mvc-tutorial/ProductManagement/publish
export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__DefaultConnection="Data Source=/home/xubuntu/aspnet-mvc-tutorial/ProductManagement/ProductManagement.db"
dotnet ProductManagement.dll
Appendix F — Final Test Suite
cd ~/aspnet-mvc-tutorial
dotnet test ProductManagementTutorial.sln -c Release
If the Release build succeeds, tests pass, production configuration is separated from development configuration, secrets are not embedded in committed files, the publish directory is generated successfully, and the published DLL can run against the intended SQLite database, Part 19 is complete.
The final tutorial will deploy the published application on Linux using Kestrel behind a reverse proxy, configure a system service, create the production data directory, apply migrations, set file permissions, inspect logs, and verify the deployed application.