Why is the app not starting? - Understanding the .NET Stack on Windows
One of the key elements to understand, as an IT professional (Mostly working with Windows) that's transitioning to DevOps or Platform Engineering, is everything that surrounds code. If you maintain servers for applications, you've likely encountered scenarios where a seemingly straightforward application fails to deploy or fails after deployment. Perhaps they've copied all the files to the right locations, but the application refuses to run. Or maybe it works on one server but not another, even though they appear identical at first glance.
The root of these problems, aside from networking and having the correct ports opened to different services if you are in an air-gapped environment, often lies in an incomplete understanding of the application stack – the complete set of software components required for an application to run properly. In this article, we'll explain application stacks fundamentals, focusing on Windows server environments and .NET applications as an example. I'll explain how the various layers interact and how to ensure your servers are properly configured before deploying code.
What Is an Application Stack?
An application stack is like a layer cake. Each layer provides essential functionality that the layers above it depend on. If any layer is missing or misconfigured, the entire application may fail to run correctly – or at all.
Consider a typical .NET web application. From bottom to top, its stack might include:
- The operating system (Windows Server)
- Required Windows features (IIS, necessary Windows components)
- Runtime environments (.NET Framework or .NET Core)
- Middleware components (ASP.NET, Entity Framework)
- The application code itself
Let's break down each of these components to understand their role in the stack.
The Foundation: Operating System and Windows Features
At the base of our application stack is the operating system. For .NET applications, this is typically a Windows Server environment. However, simply having Windows Server with runtimes installed isn't enough – you also need IIS from Windows features.
Internet Information Services (IIS)
IIS is Microsoft's web server software that handles HTTP requests and responses. For web applications, IIS is essential, but it's not a monolithic feature. IIS comprises multiple components and features, each serving a specific purpose, examples below.
- Web Server (IIS) – The core feature that enables the server to respond to HTTP requests
- IIS Management Console – The GUI tool for configuring IIS
- Basic Authentication – For simple username/password authentication
- Windows Authentication – For integrated Windows authentication
- URL Rewrite Module – For manipulating requested URLs based on defined rules
Think of IIS features as specialized tools in a toolbox. Installing all IIS features on every server would be like carrying the entire toolbox to every job when you only need a screwdriver. Understanding which features your application requires is critical for proper configuration and security.
Picking, ONLY, the necessary features is also essential for good security. We often see admins that enable all features in IIS and move on.
How Missing IIS Features or too many features Cause Problems
Imagine deploying a web application that uses Windows Authentication. If the Windows Authentication feature isn't installed on IIS, users will receive authentication errors even though the application code is perfectly valid. These issues can be perplexing because they're not caused by bugs in the code but by missing infrastructure components.
The Engines: Runtime Environments
Runtimes are the engines that execute your application code. They provide the necessary libraries and services for your application to run. In the .NET ecosystem, the most common runtimes are:
.NET Framework Runtime
The traditional .NET Framework is Windows-only and includes:
- CLR (Common Language Runtime) – Executes the compiled code
- Base Class Library – Provides fundamental types and functionality
Applications targeting specific versions of .NET Framework (e.g., 4.6.2, 4.7.2, 4.8) require that exact version installed on the server.
.NET Core/.NET Runtime
The newer, cross-platform .NET implementation includes:
- .NET Runtime – The basic runtime for console applications
- ASP.NET Core Runtime – Additional components for web applications
- .NET Desktop Runtime – Components for Windows desktop applications
- Web Hosting Bundle – Combines the ASP.NET Core Runtime with the IIS integration module
Why Runtimes Matter
Runtimes are version-specific. An application built for .NET Core 3.1 won't run on a server with only .NET 5 installed, even though .NET 5 is newer. This version specificity is a common source of deployment issues.
Consider this real-world scenario: A development team builds an application using .NET Core 3.1. The production server has .NET 5 installed. When deployed, the application fails with cryptic errors about missing assemblies. The solution isn't to fix the code but to install the correct runtime on the server.
The Bridges: Middleware and Frameworks
Between the runtime and your application code lies middleware – components that provide additional functionality beyond what the basic runtime offers. In .NET applications, this often includes:
- ASP.NET (for .NET Framework) or ASP.NET Core (for .NET Core/.NET) – For web applications
- Entity Framework – For database access
- SignalR – For real-time communications
Middleware components can have their own dependencies and version requirements. For example, an application using Entity Framework Core 3.1 needs compatible versions of other components.
The Pinnacle: Application Code
At the top of the stack sits your application code – the custom software that provides the specific functionality your users need. This includes:
- Compiled assemblies (.dll files)
- Configuration files
- Static content (HTML, CSS, JavaScript, images)
- Client-side libraries
While this is the most visible part of the stack, it cannot function without all the layers beneath it.
Bringing It All Together: A Practical Example
Let's examine a concrete example to illustrate how all these components interact:
Scenario: Deploying a .NET Core 3.1 MVC web application that uses Windows Authentication and connects to a SQL Server database.
Required stack components:
- Operating System: Windows Server 2019
- Windows Features:
- IIS Web Server
- Windows Authentication
- ASP.NET 4.8 (for backward compatibility with some components)
- Runtimes:
- .NET Core 3.1 SDK (for development servers)
- .NET Core 3.1 ASP.NET Core Runtime (for production servers)
- .NET Core 3.1 Hosting Bundle (which installs the ASP.NET Core Module for IIS)
- Middleware:
- Entity Framework Core 3.1
- Application Code:
- Your custom application DLLs
- Configuration files (appsettings.json)
- Static web content
If any component is missing from this stack, the application won't function correctly. For instance:
- Without the Windows Authentication feature, users can't log in.
- Without the .NET Core 3.1 Runtime, the application won't start.
- Without the ASP.NET Core Module, IIS won't know how to handle requests for the application.
Best Practices for Managing Application Stacks
Now that we understand what makes up an application stack, let's look at some best practices for managing them:
1. Document Your Application Stack
Create detailed documentation of every component required for your application, including specific versions. This documentation should be maintained alongside your codebase and updated whenever dependencies change.
2. CICD and Server Setup Scripts
Automate the installation and configuration of your application stack using PowerShell scripts or configuration management tools. This ensures consistency across environments and makes it easier to set up new servers.
# Example PowerShell script to install required IIS components for a .NET Core application
# Enable IIS and required features
$features = @(
'Web-Default-Doc',
'Web-Dir-Browsing',
'Web-Http-Errors',
'Web-Static-Content',
'Web-Http-Redirect',
'Web-Http-Logging',
'Web-Custom-Logging',
'Web-Log-Libraries',
'Web-ODBC-Logging',
'Web-Request-Monitor',
'Web-Http-Tracing',
'Web-Stat-Compression',
'Web-Dyn-Compression',
'Web-Filtering',
'Web-Basic-Auth',
'Web-CertProvider',
'Web-Client-Auth',
'Web-Digest-Auth',
'Web-Cert-Auth',
'Web-IP-Security',
'Web-Url-Auth',
'Web-Windows-Auth',
'Web-Net-Ext',
'Web-Net-Ext45',
'Web-AppInit',
'Web-Asp',
'Web-Asp-Net',
'Web-Asp-Net45',
'Web-ISAPI-Ext',
'Web-ISAPI-Filter',
'Web-Mgmt-Console',
'Web-Metabase',
'Web-Lgcy-Mgmt-Console',
'Web-Lgcy-Scripting',
'Web-WMI',
'Web-Scripting-Tools',
'Web-Mgmt-Service'
)
foreach ($iissharefilereq in $features){
Install-WindowsFeature $iissharefilereq -Confirm:$false
}
# Download and install .NET Core Hosting Bundle Invoke-WebRequest -Uri 'https://download.visualstudio.microsoft.com/download/pr/48d3bdeb-c0c0-457e-b570-bc2c65a4d51e/c81fc85c9319a573881b0f8b1f671f3a/dotnet-hosting-3.1.25-win.exe' -OutFile 'dotnet-hosting-3.1.25-win.exe' Start-Process -FilePath 'dotnet-hosting-3.1.25-win.exe' -ArgumentList '/quiet' -Wait # Restart IIS to apply changes net stop was /y net start w3svc
3. Use Configuration Verification
Implement scripts that verify server configurations before deployment. These scripts should check for all required components and their versions, alerting you to any discrepancies.
4. Consider Containerization
For more complex applications, consider containerization technologies like Docker. Containers package the application and its dependencies together, ensuring consistency across environments and eliminating many configuration issues.
5. Create Environment Parity
Ensure that your development, testing, and production environments have identical application stacks. This reduces the "it works on my machine" problem and makes testing more reliable.
6. Application Logging
Ensure that web.config has a logging directory to catch errors.

Common Pitfalls and How to Avoid Them
Several common pitfalls can trip up IT teams when managing application stacks:
Pitfall 1: Assuming Newer Is Always Better
Just because a newer version of a runtime or framework is available doesn't mean your application is compatible with it. Always test compatibility before upgrading components in your application stack.
Pitfall 2: Incomplete Feature Installation
When installing Windows features like IIS, it's easy to miss sub-features that your application requires. Use comprehensive installation scripts that include all necessary components.
Pitfall 3: Overlooking Dependencies
Some components have dependencies that aren't immediately obvious. For example, certain .NET features depend on specific Visual C++ Redistributable packages. Make sure to identify and install all dependencies.
Pitfall 4: Ignoring Regional and Language Settings
Applications may behave differently based on regional settings, time zones, or character encodings. Ensure these settings are consistent across your environments.
Pitfall 5: Misconfigured Permissions
Even with all the right components installed, incorrect permissions on IIS web folder level can prevent applications from running correctly. Ensure your application has the necessary permissions to access files, folders, and other resources. The app pool usually has IDs to authenticate.
Conclusion
Understanding application stacks is crucial for successful deployment and maintenance of modern applications. By recognizing that your application is more than just the code you write – it's a complex interplay of operating system features, runtimes, middleware, and your custom code – you can approach server configuration methodically and avoid mysterious deployment failures.
The next time you prepare to deploy an application, take the time to document and verify your application stack. Your future self (and your colleagues) will thank you when deployments go smoothly and applications run as expected in every environment.
Remember: Proper server configuration isn't an afterthought – it's a prerequisite for your application code to function correctly.
Django Microservices Approach with Azure Functions on Azure Container Apps
We are creating a multi-part video to explain Azure Functions running on Azure Container Apps so that we can offload some of the code out of our Django App and build our infrastructure with a microservice approach. Here’s part one and below the video a quick high-level explanation for this architecture.
Azure Functions are serverless computing units within Azure that allow you to run event-driven code without having to manage servers. They’re a great choice for building microservices due to their scalability, flexibility, and cost-effectiveness.
Azure Container Apps provide a fully managed platform for deploying and managing containerized applications. By deploying Azure Functions as containerized applications on Container Apps, you gain several advantages:
-
Microservices Architecture:
- Decoupling: Each function becomes an independent microservice, isolated from other parts of your application. This makes it easier to develop, test, and deploy them independently.
- Scalability: You can scale each function individually based on its workload, ensuring optimal resource utilization.
- Resilience: If one microservice fails, the others can continue to operate, improving the overall reliability of your application.
-
Containerization:
- Portability: Containerized functions can be easily moved between environments (development, testing, production) without changes.
- Isolation: Each container runs in its own isolated environment, reducing the risk of conflicts between different functions.
- Efficiency: Containers are optimized for resource utilization, making them ideal for running functions on shared infrastructure.
-
Azure Container Apps Benefits:
- Managed Service: Azure Container Apps handles the underlying infrastructure, allowing you to focus on your application’s logic.
- Scalability: Container Apps automatically scale your functions based on demand, ensuring optimal performance.
- Integration: It seamlessly integrates with other Azure services, such as Azure Functions, Azure App Service, and Azure Kubernetes Service.
In summary, Azure Functions deployed on Azure Container Apps provide a powerful and flexible solution for building microservices. By leveraging the benefits of serverless computing, containerization, and a managed platform, you can create scalable, resilient, and efficient applications.
Stay tuned for part 2
Containers for Data Scientists on top of Azure Container Apps
The Azure Data Science VMs are good for dev and testing and even though you could use a virtual machine scale set, that is a heavy and costly solution.
When thinking about scaling, one good solution is to containerize the Anaconda / Python virtual environments and deploy them to Azure Kubernetes Service or better yet, Azure Container Apps, the new abstraction layer for Kubernetes that Azure provides.
Here is a quick way to create a container with Miniconda 3, Pandas and Jupyter Notebooks to interface with the environment. Here I also show how to deploy this single test container it to Azure Container Apps.
The result:
A Jupyter Notebook with Pandas Running on Azure Container Apps.

Container Build
If you know the libraries you need then it would make sense to start with the lightest base image which is Miniconda3, you can also deploy the Anaconda3 container but that one might have libraries you might never use that might create unnecessary vulnerabilities top remediate.
Miniconda 3: https://hub.docker.com/r/continuumio/miniconda3
Anaconda 3: https://hub.docker.com/r/continuumio/anaconda3
Below is a simple dockerfile to build a container with pandas, openAi and tensorflow libraries.
FROM continuumio/miniconda3
RUN conda install jupyter -y --quiet && \ mkdir -p /opt/notebooks
WORKDIR /opt/notebooks
RUN pip install pandas
RUN pip install openAI
RUN pip install tensorflow
CMD ["jupyter", "notebook", "--ip='*'", "--port=8888", "--no-browser", "--allow-root"]
Build and Push the Container
Now that you have the container built push it to your registry and deploy it on Azure Container Apps. I use Azure DevOps to get the job done.

Here’s the pipeline task:
- task: Docker@2
inputs:
containerRegistry: 'dockerRepo'
repository: 'm05tr0/jupycondaoai'
command: 'buildAndPush'
Dockerfile: 'dockerfile'
tags: |
$(Build.BuildId)
latest
Deploy to Azure ContainerApps
Deploying to Azure Container Apps was painless, after understanding the Azure DevOps task, since I can include my ingress configuration in the same step as the container. The only requirement I had to do was configure DNS in my environment. The DevOps task is well documented as well but here’s a link to their official docs.
Architecture / DNS: https://learn.microsoft.com/en-us/azure/container-apps/networking?tabs=azure-cli
Azure Container Apps Deploy Task : https://github.com/microsoft/azure-pipelines-tasks/blob/master/Tasks/AzureContainerAppsV1/README.md

A few things I’d like to point out is that you don’t have to provide a username and password for the container registry the task gets a token from az login. The resource group has to be the one where the Azure Container Apps environment lives, if not a new one will be created. The target port is where the container listens on, see the container build and the jupyter notebooks are pointing to port 8888. If you are using the Container Apps Environment with a private VNET, setting the ingress to external means that the VNET can get to it not outside traffic from the internet. Lastly I disable telemetry to stop reporting.
task: AzureContainerApps@1
inputs:
azureSubscription: 'IngDevOps(XXXXXXXXXXXXXXXXXXXX)'
acrName: 'idocr'
dockerfilePath: 'dockerfile'
imageToBuild: 'idocr.azurecr.io/m05tr0/jupycondaoai'
imageToDeploy: 'idocr.azurecr.io/m05tr0/jupycondaoai'
containerAppName: 'datasci'
resourceGroup: 'IDO-DataScience-Containers'
containerAppEnvironment: 'idoazconapps'
targetPort: '8888'
location: 'East US'
ingress: 'external'
disableTelemetry: true
After deployment I had to get the token which was easy with the Log Stream feature under Monitoring. For a deployment of multiple Jupyter Notebooks it makes sense to use JupyterHub.

Boosting My Home Lab's Security and Performance with Virtual Apps from Kasm Containers
In the past I’ve worked with VDI solutions like Citrix, VMWare Horizon, Azure Virtual Desktop and others but my favorite is Kasm. For me Kasm has a DevOps friendly and modern way of doing virtual apps and virtual desktops that I didn’t find with other vendors.
With Kasm, apps and desktops run in isolated containers and I can access them easily with my browser, no need to install client software.
Here are my top 3 favorite features:
Boosting My Home Lab's Security and Performance with Virtual Apps from Kasm Containers!
#1 - Runs on the Home Lab!
Kasm Workspaces can be used to create a secure and isolated environment for running applications and browsing the web in your home lab. This can help to protect your devices from malware and other threats.
The community edition is free for 5 concurrent sessions.
If you are a Systems Admin or Engineer you can use it at home for your benefit but also to get familiar with the configuration so that you are better prepared for deploying it at work.
#2 - Low Resource Utilization
Kasm container apps are lightweight and efficient, so they can run quickly and without consuming a lot of resources. This is especially beneficial if you have a limited amount of hardware resources like on a home lab. I run mine in a small ProxMox cluster and offloads work from my main PC. You can also set the amount of compute when configuring your containerized apps.

#3 - Security
Each application is run in its own isolated container, which prevents them from interacting with each other or with your PC. This helps to prevent malware or other threats from spreading from one application to another.
The containers could run on isolated Docker networks and with a good firewall solution you can even prevent a self-replicating trojan by segmenting your network and only allowing the necessary ports and traffic flow. Example, if running the Tor Browser containerized app you could only allow it to go outbound to the internet and block SMB (Port 445) from your internal network. If the containerized app gets infected with something like the Emotet Trojan you could be preventing it from spreading further and you could kill the isolated container without having to shutdown or reformatting your local computer.
Code Vulnerability scanning: You can scan your container images in your CI/CD pipelines for vulnerabilities, which helps to identify and fix security weaknesses before you deploy them and before they can be exploited.