Windows Server Containers and Docker: Modern Application Deployment

Windows Server Containers and Docker: Modern Application Deployment

Introduction

Windows Server containers enable lightweight, portable application deployment. This guide covers Windows container fundamentals, Docker Desktop installation, creating container images, Kubernetes orchestration on Windows, container networking, persistent storage, and hybrid Windows/Linux workloads.

Windows Container Fundamentals

Container Types

Windows Server Containers (Process Isolation):

  • Shared kernel with host OS
  • Lightweight, fast startup
  • Requires matching OS version

Hyper-V Containers (Hypervisor Isolation):

  • Dedicated kernel per container
  • Stronger isolation
  • Supports mixed OS versions

Prerequisites

# Check Windows version
Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer

# Windows Server 2019+ or Windows 10/11 Pro/Enterprise required

# Enable Containers feature
Install-WindowsFeature -Name Containers -Restart

# Enable Hyper-V (for Hyper-V containers)
Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart

Installing Docker on Windows Server

Docker Engine Installation

# Install Docker EE (Enterprise Edition) for Windows Server
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
Install-Package -Name docker -ProviderName DockerMsftProvider -Force

# Start Docker service
Start-Service Docker

# Configure Docker to start automatically
Set-Service Docker -StartupType Automatic

# Verify installation
docker version
docker info

# Test with hello-world
docker run hello-world:nanoserver

Docker Desktop (Windows 10/11)

# Download Docker Desktop from https://www.docker.com/products/docker-desktop/
# Run installer: Docker Desktop Installer.exe

# Enable Windows containers mode
# Right-click Docker icon → "Switch to Windows containers..."

# Configure resources
# Settings → Resources → WSL Integration (for Linux containers)
# Settings → Resources → Advanced (CPU, Memory allocation)

Docker Configuration

# Configure Docker daemon (C:\ProgramData\docker\config\daemon.json)
$daemonConfig = @{
    "insecure-registries" = @("registry.contoso.com:5000")
    "registry-mirrors" = @("https://mirror.gcr.io")
    "log-driver" = "json-file"
    "log-opts" = @{
        "max-size" = "10m"
        "max-file" = "3"
    }
    "storage-opts" = @("size=120GB")
} | ConvertTo-Json

$daemonConfig | Out-File C:\ProgramData\docker\config\daemon.json -Encoding ASCII

# Restart Docker
Restart-Service Docker

Building Windows Container Images

Dockerfile for .NET Application

# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:6.0-nanoserver-ltsc2022

# Set working directory
WORKDIR /app

# Copy application files
COPY bin/Release/net6.0/publish/ .

# Expose port
EXPOSE 80

# Set entry point
ENTRYPOINT ["dotnet", "MyWebApp.dll"]

Building and Running Containers

# Build image
docker build -t contoso/mywebapp:1.0 .

# View images
docker images

# Run container
docker run -d `
    --name mywebapp `
    -p 8080:80 `
    contoso/mywebapp:1.0

# View running containers
docker ps

# View all containers (including stopped)
docker ps -a

# View container logs
docker logs mywebapp

# Follow logs
docker logs -f mywebapp

# Execute command in container
docker exec -it mywebapp powershell

# Stop container
docker stop mywebapp

# Remove container
docker rm mywebapp

# Remove image
docker rmi contoso/mywebapp:1.0

Multi-Stage Build Example

# Multi-stage Dockerfile
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:6.0-nanoserver-ltsc2022 AS build
WORKDIR /src

# Copy project files
COPY ["MyWebApp/MyWebApp.csproj", "MyWebApp/"]
RUN dotnet restore "MyWebApp/MyWebApp.csproj"

# Copy source and build
COPY . .
WORKDIR "/src/MyWebApp"
RUN dotnet build "MyWebApp.csproj" -c Release -o /app/build

# Stage 2: Publish
FROM build AS publish
RUN dotnet publish "MyWebApp.csproj" -c Release -o /app/publish

# Stage 3: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:6.0-nanoserver-ltsc2022 AS final
WORKDIR /app
COPY --from=publish /app/publish .
EXPOSE 80
ENTRYPOINT ["dotnet", "MyWebApp.dll"]

IIS Container Example

# IIS with ASP.NET
FROM mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2022

# Install ASP.NET
RUN powershell -Command `
    Add-WindowsFeature Web-Asp-Net45

# Copy website
COPY website/ C:/inetpub/wwwroot/

# Expose port
EXPOSE 80

# Start IIS
ENTRYPOINT ["C:\\ServiceMonitor.exe", "w3svc"]

Container Orchestration with Kubernetes

Installing Kubernetes on Windows

# Enable Kubernetes in Docker Desktop
# Docker Desktop → Settings → Kubernetes → Enable Kubernetes

# Or install minikube for testing
choco install minikube

# Start minikube with Windows containers
minikube start --driver=hyperv --container-runtime=docker

# Install kubectl
choco install kubernetes-cli

# Verify installation
kubectl version
kubectl cluster-info

Deploying Windows Containers to Kubernetes

deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mywebapp
  labels:
    app: mywebapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mywebapp
  template:
    metadata:
      labels:
        app: mywebapp
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      containers:
      - name: mywebapp
        image: contoso/mywebapp:1.0
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "256Mi"
            cpu: "500m"
          limits:
            memory: "512Mi"
            cpu: "1000m"
        env:
        - name: ASPNETCORE_ENVIRONMENT
          value: "Production"

service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: mywebapp-service
spec:
  type: LoadBalancer
  selector:
    app: mywebapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Deploying Application

# Apply deployment
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

# View deployments
kubectl get deployments

# View pods
kubectl get pods

# View services
kubectl get services

# Scale deployment
kubectl scale deployment mywebapp --replicas=5

# View pod details
kubectl describe pod <pod-name>

# View logs
kubectl logs <pod-name>

# Execute command in pod
kubectl exec -it <pod-name> -- powershell

# Delete deployment
kubectl delete -f deployment.yaml

ConfigMaps and Secrets

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  database_server: "sql.contoso.com"
  database_name: "AppDB"
  log_level: "Information"
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  database_password: "P@ssw0rd123!"
  api_key: "abc123xyz789"
# deployment with configmap and secret
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mywebapp
spec:
  template:
    spec:
      containers:
      - name: mywebapp
        image: contoso/mywebapp:1.0
        env:
        - name: DatabaseServer
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: database_server
        - name: DatabasePassword
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database_password

Container Networking

Docker Network Types

# List networks
docker network ls

# Create NAT network (default)
docker network create -d nat mynat

# Create Transparent network (external connectivity)
docker network create -d transparent mytransparent

# Create Overlay network (multi-host)
docker network create -d overlay --attachable myoverlay

# Inspect network
docker network inspect nat

# Run container with specific network
docker run -d --network=mynat --name web1 nginx:nanoserver

# Connect container to network
docker network connect mynat web1

# Disconnect container
docker network disconnect mynat web1

Container DNS and Service Discovery

# Create custom network with DNS
docker network create --driver nat --subnet=172.20.0.0/16 mynet

# Run containers on same network
docker run -d --network=mynet --name database mcr.microsoft.com/mssql/server:2019-latest
docker run -d --network=mynet --name webapp contoso/mywebapp:1.0

# Containers can communicate using container names as hostnames
# From webapp container: ping database

Port Mapping

# Map host port to container port
docker run -d -p 8080:80 contoso/mywebapp:1.0

# Map all exposed ports randomly
docker run -d -P contoso/mywebapp:1.0

# View port mappings
docker port <container-name>

# Multiple port mappings
docker run -d `
    -p 8080:80 `
    -p 8443:443 `
    contoso/mywebapp:1.0

Persistent Storage

Docker Volumes

# Create volume
docker volume create mydata

# List volumes
docker volume ls

# Inspect volume
docker volume inspect mydata

# Run container with volume
docker run -d `
    --name database `
    -v mydata:C:\data `
    mcr.microsoft.com/mssql/server:2019-latest

# Backup volume
docker run --rm `
    -v mydata:C:\source `
    -v C:\Backup:C:\backup `
    mcr.microsoft.com/windows/servercore:ltsc2022 `
    powershell -Command "Compress-Archive -Path C:\source\* -DestinationPath C:\backup\mydata.zip"

# Remove volume
docker volume rm mydata

# Remove unused volumes
docker volume prune

Bind Mounts

# Mount host directory to container
docker run -d `
    --name webapp `
    -v C:\HostData:C:\app\data `
    contoso/mywebapp:1.0

# Read-only mount
docker run -d `
    -v C:\Config:C:\app\config:ro `
    contoso/mywebapp:1.0

Kubernetes Persistent Volumes

# persistent-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-data
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: "C:\\k8s-data"
    type: DirectoryOrCreate
# persistent-volume-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: manual
# deployment with PVC
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: contoso/myapp:1.0
        volumeMounts:
        - name: data-volume
          mountPath: C:\app\data
      volumes:
      - name: data-volume
        persistentVolumeClaim:
          claimName: pvc-data

Container Registry

Azure Container Registry

# Login to ACR
az acr login --name contosoregistry

# Tag image for ACR
docker tag contoso/mywebapp:1.0 contosoregistry.azurecr.io/mywebapp:1.0

# Push image to ACR
docker push contosoregistry.azurecr.io/mywebapp:1.0

# Pull image from ACR
docker pull contosoregistry.azurecr.io/mywebapp:1.0

# Create Kubernetes secret for ACR
kubectl create secret docker-registry acr-secret `
    --docker-server=contosoregistry.azurecr.io `
    --docker-username=<service-principal-id> `
    --docker-password=<service-principal-password>

# Use secret in deployment
# spec:
#   imagePullSecrets:
#   - name: acr-secret

Private Docker Registry

# Run private registry container
docker run -d `
    -p 5000:5000 `
    --name registry `
    -v C:\registry:C:\registry `
    registry:latest

# Tag image for private registry
docker tag contoso/mywebapp:1.0 localhost:5000/mywebapp:1.0

# Push to private registry
docker push localhost:5000/mywebapp:1.0

# Pull from private registry
docker pull localhost:5000/mywebapp:1.0

Hybrid Windows/Linux Workloads

Docker Compose with Mixed Containers

docker-compose.yml:

version: '3.8'

services:
  # Linux container
  database:
    image: postgres:14
    platform: linux
    environment:
      POSTGRES_PASSWORD: P@ssw0rd123!
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - app-network

  # Windows container
  webapp:
    image: contoso/mywebapp:1.0
    platform: windows
    ports:
      - "8080:80"
    environment:
      - ConnectionStrings__Database=Host=database;Database=myapp;Username=postgres;Password=P@ssw0rd123!
    depends_on:
      - database
    networks:
      - app-network

volumes:
  db-data:

networks:
  app-network:
    driver: nat
# Start services
docker-compose up -d

# View logs
docker-compose logs

# Stop services
docker-compose down

Monitoring and Troubleshooting

Container Diagnostics

# View container resource usage
docker stats

# Inspect container
docker inspect <container-id>

# View container processes
docker top <container-name>

# Copy files from container
docker cp <container-name>:C:\logs\app.log C:\Temp\

# Copy files to container
docker cp C:\Config\app.config <container-name>:C:\app\

# Export container filesystem
docker export <container-name> -o container.tar

Kubernetes Monitoring

# View cluster events
kubectl get events --sort-by=.metadata.creationTimestamp

# View node status
kubectl get nodes -o wide

# View resource usage
kubectl top nodes
kubectl top pods

# Describe pod (detailed troubleshooting)
kubectl describe pod <pod-name>

# View pod logs (previous container)
kubectl logs <pod-name> --previous

# Port forward for debugging
kubectl port-forward pod/<pod-name> 8080:80

Key Takeaways

  • Windows Server containers enable portable application deployment
  • Process isolation shares kernel, Hyper-V isolation provides stronger security
  • Docker simplifies container image creation and management
  • Kubernetes orchestrates multi-container applications at scale
  • Container networking enables service discovery and communication
  • Persistent volumes retain data across container restarts
  • Azure Container Registry provides secure image storage
  • Docker Compose simplifies multi-container applications
  • Hybrid workloads combine Windows and Linux containers
  • Monitoring tools help diagnose container issues

Next Steps

  • Containerize existing .NET applications
  • Set up Azure Container Registry
  • Deploy Kubernetes cluster for production
  • Implement CI/CD pipeline for container images
  • Configure monitoring and logging
  • Establish container security policies

Additional Resources


Build. Ship. Run. Anywhere.