Building Windows Docker Images on Windows
This guide provides instructions on setting up and installing a Windows Server 2022 Virtual Machine on Ubuntu 22.04 to build Docker images for Windows.
Instead of building Windows Docker images on a physical machine, we set up a virtual machine to avoid OS version and kernel mismatches. Initially, I attempted to build a Windows Docker image on my Windows 10 laptop, but encountered errors during the base image pull stage.
Upon investigation, I discovered that my laptop was running Windows 10.0.26100, a Windows Insider Preview build. The error message:
hcsshim::ImportLayer failed in Win32: The system cannot find the path specified. (0x3)
indicated an incompatibility between the Windows version and the container OS version. To resolve this, I opted to use a virtual machine instead.
I used a Dell laptop with the following specifications:
- CPU: Intel(R) Core(TM) Ultra 7 165H
- Architecture: 64-bit
- Cores/Threads: 16 cores, 22 threads
First, download the Windows Server 2022 ISO from a reliable source. I downloaded it here, but I'm not sure if it's a trusted source; however, the ISO works: https://thuegpu.vn/link-download-windows-server/. Then, create a qcow2 file for the virtual machine.
First, install the QEMU program on Ubuntu:
sudo apt update
sudo apt install qemu-kvm qemu-system-x86 qemu-utils \
virt-manager libvirt-daemon-system libvirt-clients \
bridge-utilsThen, navigate to the project folder where you preferred to store the files related to the project and create a backing store for the virtual machine. The command below creates a new 100 GB disk image file called windows_vm.qcow2 using the qcow2 format. It will be used as the virtual hard disk for a virtual machine, and the file will grow in size dynamically as data is added to it, up to a maximum of 100 GB. However, later I discovered that 100GB was not enough to build the Windows Docker with the redundant packages I installed in Visual Studio, including C++ and C#. Therefore, I need to resize the partition later.
cd /home/$USER/Projects/qemu/
qemu-img create -f qcow2 windows_vm.qcow2 100GNow, boot into the Windows installer. Since my Ubuntu device has 32GB of RAM and 16 cores, I allocate half of the RAM and 10 cores for this virtual machine.
qemu-system-x86_64 \
-m 16G \
-smp 10 \
-drive file=windows_vm.qcow2,format=qcow2 \
-cdrom /home/$USER/Projects/qemu/Windows_Server_2022.iso \
-boot d \
-net nic -net user \
-enable-kvm \
-vga stdDuring installation, choose Windows Server 2022 Standard Evaluation (Desktop Experience). Be sure to manually allocate the partition that will be used to install the OS to prevent issues when resizing the disk later because the unallocated space it's not directly adjacent to the C: partition in Disk Management.
After the installation is complete, shut down the Virtual Machine to boot with another option that persists the state.
Since 100GB was insufficient, I later increased the size to 500GB:
qemu-img resize windows_vm.qcow2 500GTo verify the new size:
qemu-img info windows_vm.qcow2After booting into Windows, extend the partition:
- Open Disk Management (
diskmgmt.msc). - Locate unallocated space.
- Right-click on the C: partition and select Extend Volume.
- Follow the wizard to complete the expansion.
To enable clipboard sharing and better display support, install SPICE on Ubuntu:
sudo apt update
sudo apt install spice-vdagent virt-viewerStart QEMU with SPICE support:
qemu-system-x86_64 \
-m 16G \
-smp 10 \
-drive file=windows_vm.qcow2,format=qcow2 \
-boot c \
-enable-kvm \
-vga qxl \
-device qxl \
-device virtio-mouse-pci \
-spice port=5900,addr=127.0.0.1,disable-ticketing=on \
-device virtio-serial \
-chardev spicevmc,id=vdagent,name=vdagent \
-device virtserialport,chardev=vdagent,name=com.redhat.spice.0 \
-netdev user,id=net0 \
-device virtio-net-pci,netdev=net0To connect to the VM:
remote-viewer spice://127.0.0.1:5900Start a simple HTTP server on the host so the guest can download files directly from the guest OS. In my case, I know that the interface wlp0s20f3 is my Wi-Fi interface, so on the guest VM I will access 172.0.231.142:8000 to retrieve the files from the host machine.
cd ~/Donwloads
python3 -m http.server 8000You can use command below to findout the LAN IP address of the host and access from the guest :
ip addressDownload and install Windows SPICE Guest Tools from:
https://www.spice-space.org/download.html
In the old Windows OS, they don't ship the Edge browser with them and we only have the Internet Browser as default and many website doesn't support it. So, in that case we will need to install The display protocol SPICE + agent via Powershell: Win + X + A:
# Download SPICE Guest Tools installer
$Url = "https://www.spice-space.org/download/windows/spice-guest-tools/spice-guest-tools-latest.exe"
$Out = "$env:TEMP\spice-guest-tools.exe"
Invoke-WebRequest -Uri $Url -OutFile $Out
# Install silently
Start-Process -FilePath $Out -ArgumentList "/S" -WaitRestart the VM after installation.
These Softwares are for other things I want to test. You can skip it if you don't need them. Install Chrome browser:
# Download Chrome Enterprise installer (64-bit)
$Url = "https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi"
$Out = "$env:TEMP\chrome.msi"
Invoke-WebRequest -Uri $Url -OutFile $Out
# Install silently
Start-Process "msiexec.exe" -ArgumentList "/i `"$Out`" /qn /norestart" -WaitInstall Visual Studio Code:
# Download VS Code (64-bit system installer)
$Url = "https://update.code.visualstudio.com/latest/win32-x64/stable"
$Out = "$env:TEMP\VSCodeSetup.exe"
Invoke-WebRequest -Uri $Url -OutFile $Out
# Silent install
Start-Process -FilePath $Out -ArgumentList "/VERYSILENT /NORESTART /MERGETASKS=!runcode" -WaitOpen PowerShell as Administrator and enable Hyper-V:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
Enable-WindowsOptionalFeature -Online -FeatureName Containers -AllRestart Windows and follow the official Docker instructions:
Download Docker binaries:
Expand-Archive C:\Users\Administrator\Downloads\docker-28.0.2.zip -DestinationPath $Env:ProgramFiles
&$Env:ProgramFiles\docker\dockerd --register-service
Start-Service docker
&$Env:ProgramFiles\docker\docker run hello-world:nanoserver
icacls "C:\ProgramData\docker" /grant Everyone:F /TAfter the step above, you should have C:\Program Files\docker. If so, add this path to the system PATH environment variable:
[System.Environment]::SetEnvironmentVariable('PATH', $env:PATH + ';C:\Program Files\docker', [System.EnvironmentVariableTarget]::Machine)Make sure all the step above produce a windows environment:
docker info | Select-String "OSType"it should print out "windows".
Log in to the registry:
docker login your-registry.comBuild the image:
cd C:\Users\YourUser\Projects\your-docker-project
docker build -t windows-container .Tag and push the image:
docker tag windows-container:latest your-registry.com/your-image:tag
docker push your-registry.com/your-image:tagThis completes the setup for building Windows Docker images in a VM on Ubuntu.
These are the documented steps I collected while debugging the reason why my Windows 10 laptop cannot build Windows Docker images. I think this is valuable knowledge that I might need in the future. But why would anyone want to set up both Linux and Windows Docker side by side on a Windows machine? That would cause unnecessary conflicts. Why not just set up another Ubuntu virtual machine to make your life easier?
If needed, remove previous Docker installations:
Stop-Service docker -Force
Stop-Service docker-win -Force
taskkill /F /IM dockerd.exe
sc.exe delete docker
Remove-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\docker" -Force
Remove-Item -Path "C:\ProgramData\docker\volumes" -ForceInstall WSL on Windows to support Linux:
wsl --installdocker context use default
&$Env:ProgramFiles\docker\dockerd --register-servicedockerd.exe -H npipe:////./pipe/docker_windows --service-name docker-win --register-service
docker context create win --docker host=npipe:////./pipe/docker_windowsStart-Service docker
Start-Service docker-winCheck the active container type:
docker info | Select-String "OSType"
docker -c win info | Select-String "OSType"