Intermediate

Developing .NET Framework Apps with Docker

Run .NET Framework apps in containers — logging, config, Compose/Kubernetes modeling and troubleshooting.

Table of Contents

  1. Course Overview
  2. Module 2 — Building and Running .NET Apps in Containers
  3. Module 3 — Writing Application Logs to Containers
  4. Module 4 — Reading Config Settings from the Container Environment
  5. Module 5 — Modelling .NET Apps with Docker Compose and Kubernetes
  6. Module 6 — Troubleshooting .NET Apps in Containers
  7. Reference Tables
  8. Architecture and diagrams

1. Course Overview

This course teaches how to dockerize existing .NET Framework applications — even 10+ year-old monoliths — to run them on modern platforms like Docker and Kubernetes, without a complete code rewrite.

Key topics covered

  • Packaging .NET apps with Docker (existing artifacts or compiling from source)
  • Logging: making application logs appear as container logs
  • Configuration: injecting settings from the container environment
  • Modelling with Docker Compose and Kubernetes
  • Troubleshooting .NET apps in Windows containers

Demo application: PetShop

The demo application is a PetShop app with three components:

  • petshop-web: ASP.NET 3.5 Web Forms (legacy monolith)
  • petshop-api: .NET 4.8 REST Web API (with Entity Framework and DI)
  • petshop-db: SQL Server in a Windows container

2. Module 2 — Building and Running .NET Apps in Containers

Why containers for .NET apps?

Containers are not reserved for new microservices architectures. They bring immediate benefits to existing .NET Framework apps:

  • Consistency: same artifact from dev all the way to prod
  • Density: processes run directly on the host (no additional OS as with VMs)
  • Isolation: each component in its own container
  • Portability: a cluster can mix Linux and Windows nodes (Kubernetes, Docker Swarm, Nomad)

The value of Docker lies in the fact that everything is managed the same way, whether the app was written 15 years ago or yesterday.

Understanding Windows Containers and .NET

Docker works the same way on Linux and Windows, with one major difference: Windows containers must run on Windows machines.

┌─────────────────────────────────────────┐
│          Windows Server / Win 10        │
│  ┌──────────────┐  ┌──────────────────┐ │
│  │  Container A │  │   Container B    │ │
│  │  (web app)   │  │  (console app)   │ │
│  └──────────────┘  └──────────────────┘ │
│         Docker Engine (Windows)         │
└─────────────────────────────────────────┘
  Processes run DIRECTLY on the host
  → high density + isolation

Requirements

  • Docker Desktop on Windows 10 (Windows containers mode)
  • Docker Engine on Windows Server

Available base images

All Microsoft images are hosted on MCR (mcr.microsoft.com) and listed on Docker Hub.

ImageDescriptionUsage
windows/nanoserverUltra-lightweight, no .NET Framework, no PowerShell.NET Core / .NET 5+ / Go cross-platform
windows/servercoreFull Windows Server Core, 32/64-bit, PowerShell, .NET FX.NET Framework apps
windows/clientFull Windows 10 API (UI, GPU)Apps requiring UI or GPU
dotnet/framework/aspnet:3.5ASP.NET 3.5 on Server Core.NET 3.5 Web Forms
dotnet/framework/aspnet:4.8ASP.NET 4.8 on Server Core.NET 4.8 Web API / Web Forms
dotnet/framework/sdk:4.8.NET 4.8 SDK (MSBuild, NuGet)Compiling in container
dotnet/framework/runtime:4.8.NET 4.8 runtime only.NET 4.8 console apps

Note on version tags: ltsc2019 = Server 2019 (Long-Term Servicing Channel). Windows containers must match the host OS version.

Packaging a .NET app with a Dockerfile (artifacts)

When you already have deployable artifacts (e.g. a Web Deploy .zip file), you can package without compiling.

Example: ASP.NET 3.5 PetShop app from a zip

FROM mcr.microsoft.com/dotnet/framework/aspnet:3.5-windowsservercore-ltsc2019

# Use PowerShell for RUN commands
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# Install missing Windows Features + configure IIS
RUN Install-WindowsFeature NET-HTTP-Activation; \
    Remove-Website -Name 'Default Web Site'; \
    New-Website -Name 'petshop-web' -Port 80 -PhysicalPath 'C:\petshop-web' -Force

# Copy and extract artifacts
COPY petshop-web.zip .
RUN Expand-Archive -Path petshop-web.zip -DestinationPath /

# Overwrite the default config with container-friendly config
COPY web.config /petshop-web/

Key points:

  • SHELL ["powershell", ...]: enables PowerShell for all subsequent RUN instructions (required for Windows cmdlets)
  • Install-WindowsFeature: installs missing Windows features
  • Default image configs must be overwritten with container-appropriate configs

Multi-stage Builds — Compiling from source

Multi-stage builds allow compiling the application inside the container, eliminating any local dependencies (no Visual Studio or MSBuild needed on CI/CD).

Identifiable pattern: multiple FROM instructions in a single Dockerfile.

Complete example: .NET 4.8 Web API app (PetShop API)

# ─── Stage 1: Download LogMonitor ─────────────────────────────────────────────
FROM mcr.microsoft.com/windows/nanoserver:1809 AS logmonitor
ARG LOGMONITOR_VERSION="v1.1"
ADD https://github.com/microsoft/windows-container-tools/releases/download/${LOGMONITOR_VERSION}/LogMonitor.exe .

# ─── Stage 2: Build the application (builder) ─────────────────────────────────
FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2019 AS builder

WORKDIR /src/PetShop.Api

# Copy ONLY package files first → Docker cache optimization
COPY src/PetShop.Api/PetShop.Api.sln .
COPY src/PetShop.Api/PetShop.Api.Entities/PetShop.Api.Entities.csproj ./PetShop.Api.Entities/
COPY src/PetShop.Api/PetShop.Api.Model/PetShop.Api.Model.csproj ./PetShop.Api.Model/
COPY src/PetShop.Api/PetShop.Api.Products/PetShop.Api.Products.csproj ./PetShop.Api.Products/
COPY src/PetShop.Api/PetShop.Api.Products/packages.config ./PetShop.Api.Products/

# NuGet restore (only re-runs when .csproj files change)
RUN nuget restore PetShop.Api.sln -PackagesDirectory packages

# Copy all source code and compile
COPY src /src
RUN msbuild PetShop.Api.Products/PetShop.Api.Products.csproj \
    /p:Configuration=Release \
    /p:OutputPath=c:/out

# ─── Stage 3: Final image (runtime only) ──────────────────────────────────────
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019

ENV APP_ROOT=C:\\inetpub\\wwwroot

# Copy LogMonitor from stage 1
COPY --from=logmonitor /LogMonitor.exe /LogMonitor.exe

# Copy compiled binaries from builder
COPY --from=builder /out/_PublishedWebsites/PetShop.Api.Products ${APP_ROOT}
COPY --from=builder /src/PetShop.Api/packages/Microsoft.Data.SqlClient.SNI.2.1.1/build/net46/Microsoft.Data.SqlClient.SNI.x64.dll ${APP_ROOT}/bin

# LogMonitor and app configuration
COPY config/LogMonitorConfig.json /LogMonitor/LogMonitorConfig.json
COPY config/*.config ${APP_ROOT}/config/

# LogMonitor starts ServiceMonitor, which starts IIS
ENTRYPOINT /LogMonitor.exe /ServiceMonitor.exe w3svc

Multi-stage build advantages:

  • The final image does not contain the SDK (smaller and more secure image)
  • nuget restore is cached as long as .csproj files don’t change
  • The only requirement on the build machine: Docker

Simplified example: .NET 4.8 console app

# builder
FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2019 AS builder

WORKDIR /src/Logger.Console
COPY src /src
RUN msbuild Logger.Console/Logger.Console.csproj \
    /p:Configuration=Release \
    /p:OutputPath=c:/out

# app (runtime only, not the SDK)
FROM mcr.microsoft.com/dotnet/framework/runtime:4.8-windowsservercore-ltsc2019

WORKDIR /app
COPY --from=builder /out/ .
CMD /app/Logger.Console.exe

3. Module 3 — Writing Application Logs to Containers

How Docker collects container logs

Docker monitors the stdout/stderr streams of the container’s main process. Logs are stored as JSON by default, timestamped and indexed by stream.

Container (foreground process)
    │
    ├── stdout → docker logs
    └── stderr → docker logs

Docker’s pluggable logging systems:

  • JSON file (default)
  • ETW (Event Tracing for Windows)
  • AWS CloudWatch
  • Splunk / Graylog
  • Azure Monitor

Problem with .NET Framework IIS apps: IIS runs as a Windows background service. The container’s foreground process (e.g. ServiceMonitor) produces no logs. Logs are in files or the Event Log, invisible to Docker.

flowchart TD
    A[Container starts] --> B{App type?}
    B -->|Console app| C[stdout/stderr]
    B -->|IIS Web App| D[IIS log files]
    B -->|Windows Service| E[Event Log / files]
    C --> F[docker logs ✅]
    D --> G[LogMonitor required]
    E --> G
    G --> F

Console Logging — the easy way

For .NET Framework console apps, Docker automatically collects logs if the app writes to standard streams.

Available streams in C#:

// Standard stdout stream
Console.WriteLine("Info message");

// stderr stream
Console.Error.WriteLine("Critical error");

// Debug stream (NOT collected by Docker by default)
Debug.WriteLine("Debug message");

Docker commands to view logs:

# View all logs from a container
docker logs <container_name>

# Follow logs in real time
docker logs -f <container_name>

# View only the last 50 lines
docker logs --tail 50 <container_name>

Flexible logging with Microsoft.Extensions and Serilog

For centralized log level configuration, use the Microsoft.Extensions.Logging + Serilog pattern.

Advantages:

  • Log levels: Trace, Debug, Information, Warning, Error, Critical
  • Configuration via JSON or environment variables
  • Compatible with .NET Framework 4.x and .NET Core/5+/6+

C# code — setup in a .NET 4.8 console app:

// Load configuration (JSON + env vars)
var config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .AddJsonFile("config/logging.json", optional: true)
    .AddEnvironmentVariables()
    .Build();

// Configure Serilog via Microsoft.Extensions
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(config)
    .CreateLogger();

// DI with logging
var services = new ServiceCollection()
    .AddLogging(b => b.AddSerilog())
    .BuildServiceProvider();

Usage in a .NET 4.8 Web API controller:

public class ProductsController : ApiController
{
    private readonly PetShopContext _context;
    private readonly ILogger _logger;

    public ProductsController(PetShopContext context, ILogger<ProductsController> logger)
    {
        _context = context;
        _logger = logger;
    }

    [HttpGet]
    [Route("products")]
    public IHttpActionResult Get()
    {
        _logger.LogDebug("* GET /products called");

        var products = _context.Products.ToList();

        _logger.LogInformation($"** Returning {products.Count} products");
        return Ok(products);
    }
}

Configure the log level at runtime via environment variable:

# Information level (prod)
docker run -e Logging__LogLevel__Default=Information myapp

# Debug level (troubleshooting)
docker run -e Logging__LogLevel__Default=Debug myapp

Relaying logs with LogMonitor

LogMonitor is a Microsoft open-source tool that monitors log sources (files, ETW, Event Log) and relays them to stdout, making them visible as container logs.

LogMonitor architecture:

flowchart LR
    subgraph Container
        LM[LogMonitor.exe] --> SM[ServiceMonitor.exe]
        SM --> IIS[IIS / w3svc]
        IIS --> APP[.NET App]
        APP --> LOGFILE[(log files\nEvent Log)]
        LOGFILE --> LM
        LM --> STDOUT[stdout]
    end
    STDOUT --> DOCKERLOGS[docker logs]

LogMonitorConfig.json configuration:

{
  "LogConfig": {
    "sources": [
      {
        "type": "File",
        "directory": "C:\\inetpub\\logs\\LogFiles",
        "filter": "*.log",
        "includeSubdirectories": true
      },
      {
        "type": "EventLog",
        "channels": [
          {
            "name": "Application",
            "level": "Warning"
          }
        ]
      }
    ]
  }
}

In the Dockerfile:

# LogMonitor starts ServiceMonitor → ServiceMonitor starts IIS
ENTRYPOINT /LogMonitor.exe /ServiceMonitor.exe w3svc

Liveness chain: If IIS stops → ServiceMonitor stops → LogMonitor stops → the container stops → Docker/Kubernetes can restart the container.


4. Module 4 — Reading Config Settings from the Container Environment

Why not package config in the image

Fundamental principle: A single Docker image must be used across all environments (dev, test, UAT, prod). Configuration is injected at runtime.

❌ Anti-pattern:
image:dev  → dev config baked-in
image:uat  → UAT config baked-in
image:prod → prod config baked-in

✅ Best practice:
image:1.0  ← same image everywhere
   + environment variables / volumes at runtime

Two main reasons:

  1. Consistency: you test exactly what you deploy to prod
  2. Security: secrets are never in the image (and therefore not in the registry)

Configuration via the filesystem (volume mount)

Compatible with all versions of .NET Framework, including 3.5.

web.config pattern with configSource:

<!-- web.config main file - in the image -->
<configuration>
  <appSettings configSource="config\appsettings.config" />
  <connectionStrings configSource="config\connectionstrings.config" />
</configuration>
<!-- config\appsettings.config - in the image (default values) -->
<appSettings>
  <add key="Api.Url" value="http://petshop-api" />
  <add key="Site.Title" value="Pet Shop" />
</appSettings>

Mounting configuration at runtime:

# Replace the entire config directory at runtime
docker run -v C:\local\config:C:\petshop-web\config myapp

Important: config files must be in a separate subdirectory from the rest of the binaries. Docker replaces the entire target directory, not just individual files.

Config Builders and environment variables

For .NET 4.7.1+ apps, Microsoft’s ConfigurationBuilders allow combining multiple config sources, including environment variables.

NuGet prerequisites:

<!-- packages.config -->
<packages>
  <package id="Microsoft.Configuration.ConfigurationBuilders.Base" version="3.0.0" />
  <package id="Microsoft.Configuration.ConfigurationBuilders.Environment" version="3.0.0" />
  <!-- Optional: Azure Key Vault -->
  <package id="Microsoft.Configuration.ConfigurationBuilders.Azure" version="3.0.0" />
</packages>

web.config with ConfigBuilders:

<configuration>
  <configSections>
    <section name="configBuilders"
      type="System.Configuration.ConfigurationBuildersSection, ..." />
  </configSections>
  
  <configBuilders>
    <builders>
      <!-- Environment variables -->
      <add name="Env" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, ..." />
      <!-- Azure Key Vault (optional) -->
      <add name="AzureKV" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, ..."
           vaultName="my-keyvault" />
    </builders>
  </configBuilders>

  <!-- Env vars override file values -->
  <appSettings configBuilders="Env,AzureKV">
    <add key="Api.Url" value="http://petshop-api" />
    <add key="Site.Title" value="Pet Shop" />
  </appSettings>
</configuration>

Inject config at runtime:

docker run -e Api.Url=http://prod-api -e Site.Title="PetShop Production" myapp

Edge case: Windows IIS environment variables

Problem: Windows has two levels of environment variables:

  • Machine level: loaded at system startup
  • Process level: defined for a specific process

Docker defines variables at the process level, but IIS with a custom app pool reads variables at the machine level. Result: Docker settings are not seen by the application.

Solution: PowerShell startup script

# Dockerfile with startup script
COPY startup.ps1 /startup.ps1
ENTRYPOINT ["powershell", "-File", "C:\\startup.ps1"]
# startup.ps1 - promote process variables → machine level
Stop-Service -Name w3svc

# Copy all process variables to machine level
[System.Environment]::GetEnvironmentVariables('Process').GetEnumerator() | ForEach-Object {
    [System.Environment]::SetEnvironmentVariable($_.Key, $_.Value, 'Machine')
}

# Start LogMonitor → ServiceMonitor → IIS
& /LogMonitor.exe /ServiceMonitor.exe w3svc

This script is only needed if you create a custom IIS app pool in the Dockerfile. The standard aspnet:4.8 image already handles this automatically.


5. Module 5 — Modelling .NET Apps with Docker Compose and Kubernetes

Desired-state Application Modelling

Managing individual containers with docker run commands is fragile. Container platforms use a desired-state model: you declare the desired state and the platform maintains that state.

Three modelling levels:

graph TD
    A[Dockerfile] -->|Defines| B[Docker Image]
    B -->|Used in| C[docker-compose.yml]
    C -->|Or| D[Kubernetes YAML]
    C -->|Dev / Test| E[docker compose up]
    D -->|Prod / Cloud| F[kubectl apply]

Docker Compose — modelling and running

Docker Compose models the entire application: containers, networks, volumes, config.

Complete docker-compose.yml (3-tier PetShop):

services:

  petshop-db:
    image: psdockernetfx/petshop-db
    environment:
      - sa_password=p3t-sh##p-m5
    networks:
      - app-net

  petshop-web:
    image: psdockernetfx/petshop-web:m4
    ports:
      - "8010:80"          # host:container
    volumes:
      - type: bind
        source: .\config\web
        target: C:\petshop-web\config
    depends_on:
      - petshop-db
    networks:
      - app-net

  petshop-api:
    image: psdockernetfx/petshop-api:m4-v3
    ports:
      - "8080:80"
    environment:
      - PetShop__Web__Domain=localhost:8010
    volumes:
      - type: bind
        source: .\config\api
        target: C:\petshop-api\config
    depends_on:
      - petshop-db
    networks:
      - app-net

networks:
  app-net:

Essential Compose commands:

# Start the whole application
docker compose up -d

# View logs from all containers
docker compose logs -f

# Stop and remove containers
docker compose down

# Rebuild and restart
docker compose up -d --build

# Status
docker compose ps

Note: The modern command is docker compose (integrated into Docker CLI). The old separate docker-compose (Python) command is deprecated.

Override files and build configuration

Override files allow separating base configuration from build or environment configuration.

Main docker-compose.yml file — base model (run-time):

services:
  petshop-web:
    image: psdockernetfx/petshop-web:m4
    ports:
      - "8010:80"
    networks:
      - app-net

Override file docker-compose-build.yml — build configuration:

services:
  petshop-db:
    image: petshop-db:dev
    build:
      context: ./petshop/db

  petshop-web:
    image: petshop-web:dev
    build:
      context: ./petshop/web

  petshop-api:
    image: petshop-api:dev
    build:
      context: ./petshop/api

Using multiple Compose files:

# Merge main file + override
docker compose -f docker-compose.yml -f docker-compose-build.yml up --build

Parameterize the image with environment variables (CI/CD):

services:
  petshop-api:
    image: ${REGISTRY:-docker.io}/petshop-api:${VERSION:-1.0}
    build:
      context: ./petshop/api
      args:
        BUILD_NUMBER: ${BUILD_NUMBER}
        GIT_COMMIT: ${GIT_COMMIT}
# On the build server (Jenkins / GitHub Actions)
REGISTRY=myacr.azurecr.io VERSION=2.1.0 BUILD_NUMBER=42 \
  docker compose -f docker-compose.yml -f docker-compose-build.yml build

Multi-architecture apps in Kubernetes

For .NET Framework apps in Kubernetes, the nodeSelector is mandatory to force execution on Windows nodes.

Kubernetes Deployment for a Windows component:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: petshop-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: petshop-api
  template:
    metadata:
      labels:
        app: petshop-api
    spec:
      # REQUIRED for Windows containers
      nodeSelector:
        kubernetes.io/os: windows

      containers:
        - name: petshop-api
          image: psdockernetfx/petshop-api:m5
          ports:
            - containerPort: 80
          env:
            - name: PetShop__Web__Domain
              value: "petshop-web"
          volumeMounts:
            - name: api-config
              mountPath: C:\petshop-api\config
          resources:
            limits:
              cpu: "500m"
              memory: "512Mi"
          # Liveness probe (IIS health check)
          livenessProbe:
            httpGet:
              path: /products
              port: 80
            initialDelaySeconds: 30

      volumes:
        - name: api-config
          configMap:
            name: petshop-api-config

Creating a multi-architecture AKS cluster:

# 2 Linux nodes (for Kubernetes system components)
az aks create --name petshop-aks --node-count 2 --os-type Linux

# Add 5 Windows nodes
az aks nodepool add --cluster-name petshop-aks \
  --name winnp \
  --node-count 5 \
  --os-type Windows \
  --windows-admin-password <complex-password>

6. Module 6 — Troubleshooting .NET Apps in Containers

Troubleshooting approaches on Windows

Level 1: Docker inspection commands

# Inspect the complete configuration of a container
docker inspect <container_name>

# View running processes in the container
docker top <container_name>

# Real-time CPU/memory statistics
docker stats <container_name>

# View image metadata/labels
docker inspect --format='{{json .Config.Labels}}' <image>

Level 2: Interactive PowerShell session in the container

# Open a PowerShell session in a running container
docker exec -it <container_name> powershell

# Useful commands once inside the container:
ipconfig                          # Network config
Resolve-DnsName petshop-db        # DNS test
Get-Service w3svc                 # IIS status
Get-ChildItem env:                # Environment variables
Invoke-WebRequest http://localhost/products  # Internal HTTP test

Level 3: Custom Docker network configuration

Common issues:

  • Docker uses an IP range already used on the local network
  • Docker is not using the correct DNS server
# docker-compose.yml with custom network
networks:
  app-net:
    driver: nat
    ipam:
      config:
        - subnet: 172.20.0.0/16
    driver_opts:
      com.docker.network.driver.windows.option.dns: "10.0.0.1"

Auditing: labels in the Dockerfile

# Capture build metadata as labels
ARG BUILD_NUMBER
ARG GIT_COMMIT
ARG IMAGE_VERSION

LABEL build.number="${BUILD_NUMBER}" \
      build.git-commit="${GIT_COMMIT}" \
      build.version="${IMAGE_VERSION}"

Container Liveness and restart policies

ServiceMonitor (included in Microsoft ASP.NET images) monitors the IIS service and ensures the container stops if IIS stops.

stateDiagram-v2
    [*] --> Running : docker compose up
    Running --> Exited : IIS/Service crash
    Exited --> Running : restart policy
    Running --> Stopped : docker compose stop
    Stopped --> [*]

Docker Compose restart policies:

services:
  petshop-web:
    image: psdockernetfx/petshop-web
    restart: always          # Always restart
    # restart: on-failure    # Only on application crash
    # restart: unless-stopped # Except on explicit stop (recommended for dev)
PolicyBehavior
no (default)Never restarts
alwaysAlways restarts (crash + Docker/machine restart)
on-failureRestarts only if exit code ≠ 0
unless-stoppedLike always, but respects explicit stops

Liveness chain with LogMonitor + ServiceMonitor:

IIS crash
  → ServiceMonitor.exe stops
    → LogMonitor.exe stops (container's main process)
      → Container status = Exited
        → Docker/Kubernetes restarts the container

Debugging with Visual Studio

Visual Studio can debug code running inside a Windows container using the remote debugger.

Workflow:

  1. Right-click on the project → Add Docker Support → Visual Studio generates a debug-specific Dockerfile and docker-compose
  2. Visual Studio compiles a dev image with source code mounted as a volume
  3. The container starts with remote debugging tools enabled
  4. Visual Studio connects to the remote debugger in the container
  5. Breakpoints, step-over, variable inspection all work normally

Dockerfile generated by Visual Studio (debug):

FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019

# In debug mode, IIS root is empty
# Content is mounted via volume by Visual Studio

# Remote debugging tools mounted by Visual Studio via docker-compose
ENTRYPOINT ["C:\\ServiceMonitor.exe", "w3svc"]

Generated docker-compose.override.yml (debug):

services:
  petshop-api:
    image: petshop-api:dev
    build:
      args:
        configuration: Debug
    volumes:
      # Source code mounted into IIS root
      - .:/app
      # Remote debugger from the host machine
      - "${VSINSTALLDIR}Common7\\IDE\\Remote Debugger:C:\\remote_debugger:ro"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development

7. Reference Tables

.NET Framework base images on MCR

ImageExample tagApprox. sizeUsage
mcr.microsoft.com/windows/servercoreltsc2019~3.5 GBBase OS, 32/64-bit apps
mcr.microsoft.com/windows/nanoserver1809~250 MBModern cross-platform apps
mcr.microsoft.com/dotnet/framework/runtime4.8-windowsservercore-ltsc2019~4.5 GB.NET 4.8 console apps
mcr.microsoft.com/dotnet/framework/aspnet4.8-windowsservercore-ltsc2019~5 GB.NET 4.8 web apps
mcr.microsoft.com/dotnet/framework/aspnet3.5-windowsservercore-ltsc2019~5 GB.NET 3.5 web apps
mcr.microsoft.com/dotnet/framework/sdk4.8-windowsservercore-ltsc2019~8 GBCompilation (build stage only)

All Windows versions are available (1809, ltsc2019, 20H2, ltsc2022). Windows containers must be compatible with the host OS version.

Essential Docker commands

CommandDescription
docker build -t myapp:1.0 .Build an image from the Dockerfile in the current directory
docker run -d --name myapp myapp:1.0Start a container in detached mode
docker run -e KEY=value myapp:1.0Start with an environment variable
docker run -v C:\host\path:C:\container\path myapp:1.0Mount a bind volume
docker run -p 8080:80 myapp:1.0Publish container port 80 on host port 8080
docker logs -f myappFollow logs in real time
docker exec -it myapp powershellOpen a PowerShell session in the container
docker inspect myappView complete container config
docker statsReal-time CPU/memory statistics
docker top myappRunning processes in the container
docker stop myappGracefully stop a container
docker rm myappRemove a stopped container
docker rmi myapp:1.0Remove an image
docker pull mcr.microsoft.com/dotnet/framework/aspnet:4.8Download an image
docker push myregistry.io/myapp:1.0Publish an image to a registry

Essential Docker Compose commands

CommandDescription
docker compose up -dStart all services in detached mode
docker compose up --buildRebuild images then start
docker compose downStop and remove containers
docker compose logs -fLogs from all services
docker compose logs -f petshop-apiLogs from a specific service
docker compose psStatus of all services
docker compose exec petshop-api powershellShell in a service
docker compose restart petshop-apiRestart a service
docker compose pullUpdate all images
docker compose -f base.yml -f override.yml upUse override files

8. Architecture and Diagrams

Windows Containers Architecture — Overview

flowchart TB
    subgraph "Multi-architecture Cluster (AKS / Kubernetes)"
        subgraph "Linux Nodes"
            LN1[Linux Node 1]
            LN2[Linux Node 2]
            LN1 --- LC1[".NET Core\ncontainer"]
            LN2 --- LC2["Go\ncontainer"]
        end
        subgraph "Windows Nodes"
            WN1[Windows Node 1]
            WN2[Windows Node 2]
            WN3[Windows Node 3]
            WN1 --- WC1[".NET FX 4.8\nWeb API"]
            WN2 --- WC2[".NET FX 3.5\nWeb Forms"]
            WN3 --- WC3["SQL Server\nWindows"]
        end
    end
    INGRESS[Load Balancer / Ingress] --> LC1
    INGRESS --> WC1
    INGRESS --> WC2

Build Flow — Multi-stage Dockerfile

flowchart LR
    subgraph "docker build"
        S1["Stage 1\nnanoserver\nDownload\nLogMonitor"] 
        S2["Stage 2\nSDK 4.8\nnuget restore\nmsbuild"]
        S3["Final stage\naspnet 4.8\nRuntime only"]
    end
    SRC[Source code\n.csproj\n.cs] --> S2
    S1 -->|COPY --from=logmonitor| S3
    S2 -->|COPY --from=builder| S3
    S3 --> IMG[Final image\ncompact]

Logging Architecture in an IIS Container

flowchart TD
    subgraph Container
        LM["LogMonitor.exe\n(main process)"]
        SM["ServiceMonitor.exe"]
        IIS["IIS (w3svc)"]
        APP[".NET App\n(ASPX / Web API)"]
        LOGS["Log files\nEvent Log"]
    end
    
    LM -->|starts| SM
    SM -->|starts and monitors| IIS
    IIS --> APP
    APP -->|writes| LOGS
    LOGS -->|monitors and relays| LM
    LM -->|stdout| DOCKER["docker logs"]
    
    IIS -->|crash| SM
    SM -->|exit| LM
    LM -->|exit| EXIT["Container Exited\n→ Docker restarts"]

Configuration hierarchy

flowchart BT
    A["Default values\n(in the image)"] -->|override| B["Config files\n(volume mount)"]
    B -->|override| C["Environment variables\n(docker run -e / compose)"]
    C -->|override| D["Azure Key Vault\n(ConfigBuilder)"]
    D --> APP[.NET Application]
    
    style A fill:#d4edda
    style B fill:#cce5ff
    style C fill:#fff3cd
    style D fill:#f8d7da

Docker Compose — PetShop application model

flowchart LR
    subgraph "Docker Compose — app-net"
        WEB["petshop-web\nASP.NET 3.5\nport 8010:80"]
        API["petshop-api\n.NET 4.8 Web API\nport 8080:80"]
        DB["petshop-db\nSQL Server\nport 1433"]
    end
    
    CLIENT[Browser / Client] -->|:8010| WEB
    CLIENT -->|:8080| API
    WEB -->|depends_on| DB
    API -->|depends_on| DB
    WEB -.->|config volume\nC:\petshop-web\config| CFGWEB[(config\web)]
    API -.->|config volume\nC:\petshop-api\config| CFGAPI[(config\api)]

Additional resources:


Search Terms

.net · developing · framework · apps · docker · containerization · containers · kubernetes · application · compose · container · windows · architecture · config · configuration · environment · logging · logs · modelling · base · commands · dockerfile · essential · iis

Interested in this course?

Contact us to book it or get a custom training plan for your team.