Fixing Sitecore Docker on Windows 11 with LTSC2022
A Step-by-Step Guide to Migrating Container Base Images from LTSC2019 to LTSC2022 and Resolving Build Failures
Posted on September 20, 2026 • 10 minutes • 2006 words
Table of contents
- 📊 Introduction
- ⚠️ Why Sitecore Docker Builds Fail on Windows 11
- 🔍 Step 1: Check the Docker Daemon Before Editing Code
- 📦 Step 2: Install Docker Desktop the Right Way
- ⚙️ Step 3: Align the Solution with LTSC2022
- 🧪 Step 4: Validate Variable Resolution First
- 🏗️ Step 5: Build Local Dependency Images First
- 🛠️ Common Errors & Solutions
- 📋 Safe Validation Checklist
- 💬 Frequently Asked Questions (FAQs)
- 🏁 Conclusion
- 🧾 Credit/References
Moving a Sitecore 10.4 local container environment from older Windows Server 2019 base images to LTSC2022 is more than just swapping 1809 for ltsc2022 in your image tags.
Windows 11 manages container isolation and system processes differently than earlier releases. If your Docker daemon is set to the wrong OS, if base images mismatch across your services, or if build stages run out of sequence, local builds will crash before containers even initialize.
This guide turns a real troubleshooting session into a repeatable, step-by-step migration blueprint. We will cover the quick system checks to perform first, how to fix Docker Desktop permissions, and how to centralize your environment variables so future updates take only minutes.
Sitecore 10.4 instances rely on Windows containers. When you upgrade a development machine to Windows 11, older project configurations can trigger several hidden build issues:
- Outdated Nano Server Images: Older images hardcoded to
1809that fail under Windows 11 container isolation. - Mismatched Image Families: Mixing older
ltsc2019tags with newerltsc2022tags across child services. - Wrong Daemon Mode: Docker Desktop running its default Linux daemon instead of Windows containers.
- Missing Background Service: Docker Desktop installed per-user (
%LOCALAPPDATA%), missing the privileged service required by Windows containers. - Out-of-Order Builds: Downstream services trying to build before local base images exist.
- Accidental Variable Expansion: Database passwords containing
$signs being parsed as missing Compose variables.
BuildKit Error Hunting
Because Docker BuildKit builds targets in parallel, multiple errors often show up at the same time. Always scroll up to the very first error in your terminal. Messages such as ‘context canceled’ are simply secondary side effects of that initial failure.
Before changing your Dockerfiles or configuration, verify that Docker Desktop is actually set up to run Windows containers.
Run this command in PowerShell:
docker info | Select-String "OSType"You need to see:
OSType: windowsIf the output is OSType: linux, Docker Desktop is running its Linux daemon. Setting platform: windows/amd64 in your docker-compose.yml does not change the daemon engine for you. Trying to build a Windows image on a Linux daemon produces misleading errors:
unable to find user ContainerUser: no matching entries in passwd fileNext, confirm your exact host OS build version:
Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsBuildNumberWindows 11 supports Windows Server 2022 containers natively, but the Docker Desktop daemon must be in Windows mode before running builds.
Docker Desktop has two installation modes on Windows, and the difference is critical:
- Per-User Installation: Installs in
%LOCALAPPDATA%\Programs\DockerDesktop. It supports Linux containers only. - All-Users Installation: Installs in
C:\Program Files\Docker\Docker. It installs the privileged system helper and fully supports Windows containers.
For an x64 Windows 11 workstation running Sitecore, download the x86_64 installer and run it with administrator permissions for all users. Do not choose the ARM build unless you are on an ARM64 machine.
The all-users install registers com.docker.service, which is required for Windows containers and Hyper-V isolation. Verify that it is running:
Get-Service com.docker.serviceIf another administrator set up your computer, add your account to the local docker-users group:
net localgroup docker-users "DOMAIN\username" /addSession Token Refresh
Sign out and sign back in after changing group membership so Windows creates a new security token for your user account.
Confirm your membership:
whoami /groups | Select-String "docker-users"If you are running Docker inside a virtual machine (VM) or virtual desktop infrastructure (VDI), make sure nested virtualization is enabled in the hypervisor host settings. Enabling Hyper-V inside the guest OS alone will not work if the host hypervisor blocks virtualization extensions.
Instead of scattering hardcoded image tags across multiple Dockerfiles, centralize your image tags inside your .env file:
SITECORE_VERSION=10.4.1-ltsc2022
EXTERNAL_IMAGE_TAG_SUFFIX=ltsc2022
TRAEFIK_IMAGE=traefik:v3.6.4-windowsservercore-ltsc2022
NODEJS_PARENT_IMAGE=mcr.microsoft.com/windows/nanoserver:ltsc2022
ARTIFACTS_IMAGE=mcr.microsoft.com/windows/nanoserver:ltsc2022
WINDOWS_CONTAINER_PLATFORM=windows/amd64
NODEJS_VERSION=24.2.0
TRAEFIK_ISOLATION=hyperv
# Othe required changes for Sitecore LTSC2022 images
TOOLS_IMAGE=scr.sitecore.com/tools/sitecore-docker-tools-assets:10.2.0-ltsc2022
MANAGEMENT_SERVICES_IMAGE=scr.sitecore.com/sxp/modules/sitecore-management-services-xm1-assets:5.2.113-ltsc2022
HEADLESS_SERVICES_IMAGE=scr.sitecore.com/sxp/modules/sitecore-headless-services-xm1-assets:22.0.7-ltsc2022
# Sitecore SXA and SPE versions.
SPE_VERSION=7.0-ltsc2022
SXA_VERSION=10.4.0-ltsc2022
TOOLS_VERSION=10.4.0-ltsc2022
SITECORE_TOOLS_REGISTRY=scr.sitecore.com/tools/
# -- Headless SXA -- :: End :: ---
SITECORE_ID_VERSION=8.0-ltsc2022Pass those values cleanly through docker-compose.yml:
services:
traefik:
isolation: ${TRAEFIK_ISOLATION}
image: ${TRAEFIK_IMAGE}
id:
isolation: ${ISOLATION}
image: ${SITECORE_DOCKER_REGISTRY}sitecore-identity:${SITECORE_ID_VERSION}
environment:
Sitecore_Sitecore__IdentityServer__SitecoreMemberShipOptions__ConnectionString: Data Source=${SQL_SERVER};Initial Catalog=${SQL_DATABASE_PREFIX}.Core;User ID=${SQL_SA_LOGIN};Password=${SQL_SA_PASSWORD};Encrypt=true;TrustServerCertificate=true;
labels:
# ADD THIS EXACT LINE BELOW FOR TRAEFIK V3:
- "traefik.http.services.id.loadbalancer.server.port=80"
cd:
labels:
# ADD THIS EXACT LINE BELOW FOR TRAEFIK V3:
- "traefik.http.services.cd.loadbalancer.server.port=80"
cm:
labels:
# ADD THIS EXACT LINE BELOW FOR TRAEFIK V3:
- "traefik.http.services.cm.loadbalancer.server.port=80" Update for the docker-compose-override.yml:
services:
nodejs:
platform: ${WINDOWS_CONTAINER_PLATFORM}
isolation: ${SERVICE_ISOLATION}
build:
context: ./docker/build/nodejs
args:
PARENT_IMAGE: ${NODEJS_PARENT_IMAGE}
solution:
platform: ${WINDOWS_CONTAINER_PLATFORM}
build:
context: ./
args:
ARTIFACTS_IMAGE: ${ARTIFACTS_IMAGE}
rendering:
platform: ${WINDOWS_CONTAINER_PLATFORM}
labels:
# ADD THIS EXACT LINE BELOW FOR TRAEFIK V3:
- "traefik.http.services.rendering-nextjs.loadbalancer.server.port=3000"
# Mount the Traefik configuration and certs.
traefik:
platform: ${WINDOWS_CONTAINER_PLATFORM}
mssql:
platform: ${WINDOWS_CONTAINER_PLATFORM}
mssql-init:
platform: ${WINDOWS_CONTAINER_PLATFORM}
solr:
platform: ${WINDOWS_CONTAINER_PLATFORM}
solr-init:
platform: ${WINDOWS_CONTAINER_PLATFORM}
id:
platform: ${WINDOWS_CONTAINER_PLATFORM}
image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-id8:${VERSION:-latest}
build:
context: ./docker/build/id
args:
PARENT_IMAGE: ${SITECORE_DOCKER_REGISTRY}sitecore-identity:${SITECORE_ID_VERSION}
cd:
platform: ${WINDOWS_CONTAINER_PLATFORM}
image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-xm1-cd:${VERSION:-latest}
build:
context: ./docker/build/cd
args:
TOOLS_IMAGE: ${SITECORE_TOOLS_REGISTRY}sitecore-docker-tools-assets:${TOOLS_VERSION}
depends_on:
solution:
condition: service_started
cm:
platform: ${WINDOWS_CONTAINER_PLATFORM}
build:
context: ./docker/build/cm
args:
TOOLS_IMAGE: ${SITECORE_TOOLS_REGISTRY}sitecore-docker-tools-assets:${TOOLS_VERSION}You can also enforce required variables directly in Compose. This fails early with a clear warning instead of passing an empty image string:
ARTIFACTS_IMAGE: ${ARTIFACTS_IMAGE:?ARTIFACTS_IMAGE is required}Multi-stage builds can easily hide outdated base tags. Declare any build arguments used in FROM statements before the first stage:
ARG BUILD_IMAGE
ARG ARTIFACTS_IMAGE
FROM ${BUILD_IMAGE} AS builder
# Solution compile steps
FROM ${ARTIFACTS_IMAGE}
COPY --from=builder /build/artifacts /artifactsAn ARG declared inside one stage is not available to later FROM statements. If an argument is missing or out of scope, BuildKit will throw an error:
base name (${ARTIFACTS_IMAGE}) should not be blankNot every image should be upgraded at the same time:
- Nano Server: Update older
1809tags toltsc2022where your tools support it. - .NET Framework SDK: Review this separately. If your build uses
4.8-windowsservercore-ltsc2019, make sure your custom build scripts compile cleanly under LTSC2022 before swapping tags. - Reverse Proxies (Traefik): Moving from Traefik v2 to v3 is a major version upgrade with breaking configuration changes. Do not bundle it with an OS migration unless necessary.
- Node.js: Verify that your front-end rendering packages and plugins work with newer Node versions before upgrading.
Before triggering a lengthy build, verify how Docker Compose interprets your variables:
docker compose config --environmentThen check the merged Compose configuration:
docker compose `
-f .\docker-compose.yml `
-f .\docker-compose.override.xm1.yml `
configThis step highlights invalid YAML formatting, missing variables, or broken paths in seconds.
Handling Dollar Signs in Passwords
If you see a warning like ‘The “G” variable is not set’, check your passwords or secret tokens. Docker Compose treats $G as a variable. Escape dollar signs by doubling them ($$) in your configuration files or store secrets safely in your .env file.
Some Dockerfiles depend on custom local images created earlier in your solution. For example, your rendering container may rely on your custom Node.js image, while your Content Management (CM) container copies files from your local solution build.
If Docker Compose builds services out of order, it may try to pull your local image name (like myproject-solution:latest) from Docker Hub:
pull access denied for myproject-solution, repository does not existTo prevent this, build your foundation dependency images first:
# Step 1: Build local base and asset images
docker compose `
-f .\docker-compose.yml `
-f .\docker-compose.override.xm1.yml `
build nodejs solution
# Step 2: Build the remaining services
docker compose `
-f .\docker-compose.yml `
-f .\docker-compose.override.xm1.yml `
build| Error or Warning | Likely Cause | First Check |
|---|---|---|
no match for platform in manifest | Wrong daemon OS, bad image tag, or platform mismatch | Run docker info and inspect the exact image tag |
unable to find user ContainerUser | Windows image running against a Linux daemon | Confirm OSType: windows before editing the Dockerfile |
base name (...) should not be blank | Missing build argument or out-of-scope Dockerfile ARG | Run docker compose config; declare ARG before the first FROM |
pull access denied for a local image | Dependent image was not built locally first | Build base service images (solution, nodejs) first |
com.docker.service is missing | Docker Desktop was installed per-user | Reinstall Docker Desktop with admin rights for all users |
User not in docker-users | Missing local group rights or stale sign-in token | Run net localgroup docker-users, then sign out and back in |
context canceled | A different parallel build step failed first | Find the earliest non-cancellation error in console output |
| Compose says variable is unset | A $ in a password was treated as variable interpolation | Run docker compose config --environment and escape $ with $$ |
Run these checks in order whenever setting up or troubleshooting a machine:
# 1. Confirm host OS and daemon mode
Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsBuildNumber
docker info | Select-String "OSType"
# 2. Verify Docker service and local permissions
Get-Service com.docker.service
net localgroup docker-users
# 3. Resolve Compose variables and validate syntax
docker compose config --environment
docker compose -f .\docker-compose.yml -f .\docker-compose.override.xm1.yml config --quiet
# 4. Confirm LTSC2022 variables are set
Get-Content .env | Select-String "SITECORE_VERSION|EXTERNAL_IMAGE_TAG_SUFFIX|NODEJS_PARENT_IMAGE|ARTIFACTS_IMAGE"
# 5. Build base dependency images before the full topology
docker compose -f .\docker-compose.yml -f .\docker-compose.override.xm1.yml build nodejs solution
# 6. Build and start the environment
docker compose -f .\docker-compose.yml -f .\docker-compose.override.xm1.yml build
docker compose -f .\docker-compose.yml -f .\docker-compose.override.xm1.yml up -dAvoid Unnecessary Pruning
Avoid running ‘docker system prune –all –volumes’ as a routine diagnostic step. It wipes all cached layers, downloaded images, and database volumes across all your local projects.
Docker Compose uses bash-style variable substitution, so any $VARIABLE or ${VARIABLE} syntax is evaluated as an environment variable. If your password contains a dollar sign (e.g., P@
) in your configuration files, or store the password securely in your .env file.
A successful Sitecore Docker LTSC2022 migration
starts by proving that Docker Desktop
is installed system-wide and actively running in Windows container mode. Once you confirm OSType: windows, aligning your image tags, centralizing variables in .env, validating your Compose configuration, and building foundation dependencies in the right order will keep your local developer environment stable and fast.
Following this sequence prevents hours of unnecessary Dockerfile edits caused by symptoms of the wrong daemon mode or missing system services.



