Table of Contents
- Course Overview
- Module 2 — Building and Running .NET Apps in Containers
- Module 3 — Writing Application Logs to Containers
- Module 4 — Reading Config Settings from the Container Environment
- Module 5 — Modelling .NET Apps with Docker Compose and Kubernetes
- Module 6 — Troubleshooting .NET Apps in Containers
- Reference Tables
- 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
devall the way toprod - 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.
| Image | Description | Usage |
|---|---|---|
windows/nanoserver | Ultra-lightweight, no .NET Framework, no PowerShell | .NET Core / .NET 5+ / Go cross-platform |
windows/servercore | Full Windows Server Core, 32/64-bit, PowerShell, .NET FX | .NET Framework apps |
windows/client | Full Windows 10 API (UI, GPU) | Apps requiring UI or GPU |
dotnet/framework/aspnet:3.5 | ASP.NET 3.5 on Server Core | .NET 3.5 Web Forms |
dotnet/framework/aspnet:4.8 | ASP.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 subsequentRUNinstructions (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 restoreis cached as long as.csprojfiles 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 →
ServiceMonitorstops →LogMonitorstops → 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:
- Consistency: you test exactly what you deploy to prod
- 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.8image 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 separatedocker-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)
| Policy | Behavior |
|---|---|
no (default) | Never restarts |
always | Always restarts (crash + Docker/machine restart) |
on-failure | Restarts only if exit code ≠ 0 |
unless-stopped | Like 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:
- Right-click on the project → Add Docker Support → Visual Studio generates a debug-specific
Dockerfileanddocker-compose - Visual Studio compiles a
devimage with source code mounted as a volume - The container starts with remote debugging tools enabled
- Visual Studio connects to the remote debugger in the container
- 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
| Image | Example tag | Approx. size | Usage |
|---|---|---|---|
mcr.microsoft.com/windows/servercore | ltsc2019 | ~3.5 GB | Base OS, 32/64-bit apps |
mcr.microsoft.com/windows/nanoserver | 1809 | ~250 MB | Modern cross-platform apps |
mcr.microsoft.com/dotnet/framework/runtime | 4.8-windowsservercore-ltsc2019 | ~4.5 GB | .NET 4.8 console apps |
mcr.microsoft.com/dotnet/framework/aspnet | 4.8-windowsservercore-ltsc2019 | ~5 GB | .NET 4.8 web apps |
mcr.microsoft.com/dotnet/framework/aspnet | 3.5-windowsservercore-ltsc2019 | ~5 GB | .NET 3.5 web apps |
mcr.microsoft.com/dotnet/framework/sdk | 4.8-windowsservercore-ltsc2019 | ~8 GB | Compilation (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
| Command | Description |
|---|---|
docker build -t myapp:1.0 . | Build an image from the Dockerfile in the current directory |
docker run -d --name myapp myapp:1.0 | Start a container in detached mode |
docker run -e KEY=value myapp:1.0 | Start with an environment variable |
docker run -v C:\host\path:C:\container\path myapp:1.0 | Mount a bind volume |
docker run -p 8080:80 myapp:1.0 | Publish container port 80 on host port 8080 |
docker logs -f myapp | Follow logs in real time |
docker exec -it myapp powershell | Open a PowerShell session in the container |
docker inspect myapp | View complete container config |
docker stats | Real-time CPU/memory statistics |
docker top myapp | Running processes in the container |
docker stop myapp | Gracefully stop a container |
docker rm myapp | Remove a stopped container |
docker rmi myapp:1.0 | Remove an image |
docker pull mcr.microsoft.com/dotnet/framework/aspnet:4.8 | Download an image |
docker push myregistry.io/myapp:1.0 | Publish an image to a registry |
Essential Docker Compose commands
| Command | Description |
|---|---|
docker compose up -d | Start all services in detached mode |
docker compose up --build | Rebuild images then start |
docker compose down | Stop and remove containers |
docker compose logs -f | Logs from all services |
docker compose logs -f petshop-api | Logs from a specific service |
docker compose ps | Status of all services |
docker compose exec petshop-api powershell | Shell in a service |
docker compose restart petshop-api | Restart a service |
docker compose pull | Update all images |
docker compose -f base.yml -f override.yml up | Use 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