How to Use Docker Compose on Windows Server 2025

Docker Compose transforms the management of multi-container applications from a series of individual docker run commands into a single declarative YAML file. On Windows Server 2025, Compose supports Windows containers natively, enabling you to describe a full application stack — web frontend, application server, and SQL Server database — in a single compose.yaml file and bring everything up or down with one command. This guide covers installing the Compose CLI plugin on Windows Server, authoring a realistic Windows application stack with IIS and SQL Server, configuring volumes and networking, working with environment variables, and managing the lifecycle of the stack from deployment through teardown.

Prerequisites

  • Docker Engine installed and running on Windows Server 2025
  • PowerShell 5.1 or later running as Administrator
  • At least 8 GB of RAM available (SQL Server container requires approximately 2 GB)
  • 20 GB or more of free disk space for images
  • Internet access to pull images from MCR and Docker Hub
  • Familiarity with YAML syntax and basic Docker concepts

Step 1: Install Docker Compose on Windows Server 2025

Docker Desktop bundles Compose automatically, but on Windows Server you install Docker Engine headlessly, which means Compose must be installed separately. The modern approach is to install Compose V2 as a CLI plugin, which integrates as docker compose (space, not hyphen) rather than the legacy standalone binary.

# Find the latest Compose V2 release from GitHub
$ComposeVersion = "2.27.1"
$ComposeUrl = "https://github.com/docker/compose/releases/download/v$ComposeVersion/docker-compose-windows-x86_64.exe"

# Download the Compose binary
$PluginDir = "$env:ProgramFilesDockercli-plugins"
New-Item -ItemType Directory -Force -Path $PluginDir

Invoke-WebRequest -Uri $ComposeUrl -OutFile "$PluginDirdocker-compose.exe"

# Verify installation
docker compose version

# Alternative: install the legacy standalone binary (v1-compatible)
$LegacyUrl = "https://github.com/docker/compose/releases/download/v$ComposeVersion/docker-compose-windows-x86_64.exe"
Invoke-WebRequest -Uri $LegacyUrl -OutFile "C:WindowsSystem32docker-compose.exe"
docker-compose --version

Step 2: Create the Application Working Directory and .env File

Compose reads a .env file in the same directory as compose.yaml and interpolates the variables into the YAML. Storing secrets and environment-specific values in a .env file (excluded from source control) keeps the compose.yaml portable across environments.

# Create a working directory for the application stack
$AppDir = "C:AppsWebStack"
New-Item -ItemType Directory -Force -Path $AppDir
Set-Location $AppDir

# Create the .env file (never commit this to version control)
@'
# SQL Server configuration
SA_PASSWORD=P@ssw0rd!Str0ng2025
MSSQL_PID=Developer
MSSQL_COLLATION=SQL_Latin1_General_CP1_CI_AS

# IIS application configuration
APP_ENV=production
APP_PORT=8080

# Stack-level metadata
COMPOSE_PROJECT_NAME=webstack
'@ | Set-Content -Path .env -Encoding UTF8

# Confirm the file is readable
Get-Content .env

Step 3: Author the compose.yaml for IIS and SQL Server

The following compose.yaml defines two services: an IIS-based web frontend and a SQL Server 2022 backend. Both run as Windows process-isolated containers. A named volume persists SQL Server data across restarts, and a dedicated bridge network isolates the stack.

# Write the compose.yaml file
@'
name: webstack

services:

  web:
    image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2022
    isolation: process
    ports:
      - "${APP_PORT}:80"
    volumes:
      - type: bind
        source: C:AppsWebStackwwwroot
        target: C:inetpubwwwroot
      - type: bind
        source: C:AppsWebStackiis-logs
        target: C:inetpublogsLogFiles
    environment:
      - APP_ENV=${APP_ENV}
    networks:
      - frontend
      - backend
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "powershell", "-Command",
             "try { Invoke-WebRequest http://localhost -UseBasicParsing -TimeoutSec 5 | Out-Null; exit 0 } catch { exit 1 }"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    isolation: process
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=${SA_PASSWORD}
      - MSSQL_PID=${MSSQL_PID}
      - MSSQL_COLLATION=${MSSQL_COLLATION}
    volumes:
      - sqldata:C:Program FilesMicrosoft SQL ServerMSSQL16.MSSQLSERVERMSSQLDATA
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "powershell", "-Command",
             "try { Invoke-SqlCmd -ServerInstance localhost -Query 'SELECT 1' -Username sa -Password $env:SA_PASSWORD | Out-Null; exit 0 } catch { exit 1 }"]
      interval: 30s
      timeout: 15s
      retries: 5
      start_period: 120s

volumes:
  sqldata:
    driver: local

networks:
  frontend:
    driver: nat
  backend:
    driver: nat
    internal: true
'@ | Set-Content -Path compose.yaml -Encoding UTF8

# Create the bind-mount directories on the host
New-Item -ItemType Directory -Force -Path C:AppsWebStackwwwroot
New-Item -ItemType Directory -Force -Path C:AppsWebStackiis-logs

# Create a test HTML page
@'


IIS + SQL Server Stack on Windows Server 2025

Deployed via Docker Compose.

'@ | Set-Content -Path C:AppsWebStackwwwrootindex.html -Encoding UTF8

Step 4: Start the Stack and Validate

Use docker compose up -d to pull any missing images and start all services in detached mode. The depends_on with condition: service_healthy ensures the web service does not start until SQL Server passes its health check.

# Pull all images before starting (useful for offline validation)
docker compose pull

# Start the full stack in detached mode
docker compose up -d

# Watch the startup process
docker compose ps

# Follow logs for all services simultaneously
docker compose logs -f

# Follow logs for a single service
docker compose logs -f db

# Inspect the health status of all containers
docker compose ps --format "table {{.Name}}t{{.Status}}t{{.Health}}"

# Test the web frontend
Invoke-WebRequest -Uri http://localhost:8080 -UseBasicParsing

Step 5: Scale, Update, and Manage the Stack

Compose makes it straightforward to scale stateless services, roll out image updates, and run one-off administrative commands inside a container. Note that scaling a service that uses bind-mounted host paths requires care to avoid path conflicts between replicas.

# Scale the web service to 3 replicas (only valid for stateless services)
docker compose up -d --scale web=3

# Pull updated images and recreate changed services without downtime
docker compose pull
docker compose up -d --no-deps web

# Run a one-off PowerShell command inside the running db container
docker compose exec db powershell -Command `
    "Invoke-Sqlcmd -ServerInstance localhost -Query 'SELECT name FROM sys.databases' -Username sa -Password $env:SA_PASSWORD"

# View resource usage for all Compose services
docker compose stats --no-stream

# Restart a single service
docker compose restart web

Step 6: Configure Named Volumes and Volume Lifecycle

Named volumes managed by Docker persist data independently of the container lifecycle. Understanding how to back up, restore, and prune volumes is essential for production SQL Server deployments.

# List all Docker volumes for this Compose project
docker volume ls --filter "label=com.docker.compose.project=webstack"

# Inspect the SQL data volume
docker volume inspect webstack_sqldata

# Back up the SQL data volume to a tar archive on the host
docker run --rm --isolation=process `
    -v webstack_sqldata:C:data `
    -v C:Backups:C:backup `
    mcr.microsoft.com/windows/servercore:ltsc2022 `
    powershell -Command "Compress-Archive -Path C:data* -DestinationPath C:backupsqldata-backup.zip -Force"

# Remove the stack but KEEP the named volume
docker compose down

# Remove the stack AND the named volume (destructive — data is lost)
docker compose down -v

# Remove dangling volumes not associated with any container
docker volume prune -f

Step 7: Health Checks, Restart Policies, and Compose Down

Production stacks require robust health checks and restart policies to recover automatically from transient failures. The restart: unless-stopped policy defined in the compose file keeps services running through Docker daemon restarts, while health checks gate dependent service startup and surface container status in docker compose ps.

# Inspect detailed health check output for a container
$ContainerId = docker compose ps -q db
docker inspect $ContainerId --format "{{json .State.Health}}" | ConvertFrom-Json | Select-Object -ExpandProperty Log

# Force a health check re-run (useful during debugging)
docker inspect $ContainerId --format "{{.State.Health.Status}}"

# Gracefully stop the stack (sends SIGTERM, waits for containers to stop)
docker compose stop

# Stop and remove containers, networks (volumes preserved)
docker compose down

# Stop, remove containers, networks, AND volumes
docker compose down -v --remove-orphans

# Remove all images used by the stack
docker compose down --rmi all

Conclusion

Docker Compose on Windows Server 2025 provides a practical and reproducible way to manage multi-container Windows application stacks. This guide covered installing the Compose CLI plugin, structuring a compose.yaml with IIS and SQL Server services, managing named volumes and bind mounts with Windows paths, using .env files to separate configuration from code, and operating the full stack lifecycle from startup through teardown. One important limitation to keep in mind is that mixing Linux containers with Windows containers in the same Compose file requires WSL2 with Docker Desktop — a feature not available on Server Core installations of Windows Server 2025. For mixed-OS workloads, consider a dedicated Linux Docker host alongside your Windows host, orchestrated through a shared Kubernetes cluster or a Docker Swarm with mixed-OS node support.