Repository: moby/moby (language: go)

Issue:
CI: can we reduce duration of Windows CI?
### Description

This is always painful to watch when (re)running CI on Windows:

<img width="618" alt="Screenshot 2023-08-17 at 17 01 02" src="https://github.com/moby/moby/assets/1804568/48ffc637-f7cc-40b4-91f9-4e231726a3f7">

TL;DR: **Pulling and building the image takes longer than it takes for the tests to run** 

So I'm wondering if we can improve that.

- Windows images are large, so pulling will always take time: do the GitHub runners have some Windows base images cached? Could we benefit from that by switching to a different base image?
- Windows doesn't support BuildKit yet; could we use buildkit and cross-compile?
- ^^ how do we share the image between a Linux runner and a Windows runner? (if we do need to push somewhere, pushing itself should only build the non-base image layers, so could be small, but that's assuming the base-image would already be cached on the Windows runner).

For reference, I took a quick look at a run of unit-tests on Windows 2022;


Pulling the base-image takes ~4 minutes:

```
2023-08-17T14:43:09.0141141Z ltsc2022: Pulling from windows/servercore
2023-08-17T14:43:09.2065229Z d7662b0a97ab: Already exists
2023-08-17T14:43:09.2817325Z fd2627c0341e: Pulling fs layer
2023-08-17T14:43:12.1307495Z fd2627c0341e: Verifying Checksum
2023-08-17T14:43:12.1308182Z fd2627c0341e: Download complete
2023-08-17T14:47:08.3329931Z fd2627c0341e: Pull complete
2023-08-17T14:47:08.3741959Z Digest: sha256:f1974546e8a73746e6c60781895341dd7a6760e28534d3d0cc5f3c88c1f5825a
2023-08-17T14:47:08.3848963Z Status: Downloaded newer image for mcr.microsoft.com/windows/servercore:ltsc2022
2023-08-17T14:47:08.3928675Z mcr.microsoft.com/windows/servercore:ltsc2022
```

Sending build-context a few seconds:

```
2023-08-17T14:47:18.1676773Z Sending build context to Docker daemon  80.71MB
2023-08-17T14:47:18.1677488Z 
2023-08-17T14:47:19.1315389Z Step 1/13 : FROM microsoft/windowsservercore
```

Building the image .... 5 minutes:

```
2023-08-17T14:47:19.1315389Z Step 1/13 : FROM microsoft/windowsservercore
...
2023-08-17T14:51:31.9983889Z Step 13/13 : COPY . .
2023-08-17T14:52:15.2890434Z  ---> 3e5d7a4ecb9f
2023-08-17T14:52:17.4209818Z Successfully built 3e5d7a4ecb9f
2023-08-17T14:52:17.4542977Z Successfully tagged moby:test
```

Then we have some steps to copy the results out, because we can't run docker-in-docker on Windows, so we copy the Go binaries and the bundles out, which takes less than a second;

```
2023-08-17T14:52:18.4562190Z ##[group]Run & docker run --name moby -e "DOCKER_GITCOMMIT=cfedd125599abef1dc05946205a782642275dabf" `
2023-08-17T14:52:18.4568026Z & docker run --name moby -e "DOCKER_GITCOMMIT=cfedd125599abef1dc05946205a782642275dabf" `
2023-08-17T14:52:18.4568710Z   -v "D:\a\moby\moby\go-build:C:\Users\ContainerAdministrator\AppData\Local\go-build" `
2023-08-17T14:52:18.4569301Z   -v "D:\a\moby\moby\go\pkg\mod:C:\gopath\pkg\mod" `
2023-08-17T14:52:18.4569920Z   -v "D:\a\moby\moby\go\src\github.com\docker\docker\bundles:C:\gopath\src\github.com\docker\docker\bundles" `
2023-08-17T14:52:18.4570490Z   moby:test hack\make.ps1 -TestUnit
2023-08-17T14:52:18.4639661Z shell: C:\Program Files\PowerShell\7\pwsh.EXE -command ". '{0}'"
```

And then we start the unit-tests (which takes around 7 minutes);

```
2023-08-17T14:52:24.3570908Z INFO: make.ps1 starting at 08/17/2023 14:52:24
2023-08-17T14:52:24.6558308Z INFO: Git commit (cfedd125599abef1dc05946205a782642275dabf) assumed from DOCKER_GITCOMMIT environment variable
2023-08-17T14:52:24.6863877Z INFO: Running unit tests...
...
2023-08-17T14:59:21.8806727Z DONE 2709 tests, 65 skipped in 407.846s
2023-08-17T14:59:22.0738675Z INFO: make.ps1 ended at 08/17/2023 14:59:22
```




Discussion / hints:
/cc @crazy-max @tonistiigi perhaps you have ideas
> how do we share the image between a Linux runner and a Windows runner?

I guess, because we can't run docker-in-docker, we could even skip the "image", and only the artefacts 🤔 (although some things (non-integration tests?) may still run inside the Dockerfile, I'd have to check exactly what.
If it's just about re-running failed test job then we could have a dedicated job building the image and export it and make test jobs depend on it so re-running would not rebuild the whole thing.
Yeah, I noticed it on a failed "unit test" on Windows; unit-tests should not take long to run, and then I noticed ... well.. they still take quite some time because it had to rebuild the image. So I thought; let me at least write down what I found. 😄 

Fix patch:
diff --git a/.github/workflows/.windows.yml b/.github/workflows/.windows.yml
index 5e8736e34de9b..5392a5a854da1 100644
--- a/.github/workflows/.windows.yml
+++ b/.github/workflows/.windows.yml
@@ -75,9 +75,12 @@ jobs:
       -
         name: Build base image
         run: |
-          docker pull ${{ env.WINDOWS_BASE_IMAGE }}:${{ env.WINDOWS_BASE_IMAGE_TAG }}
-          docker tag ${{ env.WINDOWS_BASE_IMAGE }}:${{ env.WINDOWS_BASE_IMAGE_TAG }} microsoft/windowsservercore
-          docker build --build-arg GO_VERSION -t ${{ env.TEST_IMAGE_NAME }} -f Dockerfile.windows .
+          & docker build `
+            --build-arg WINDOWS_BASE_IMAGE `
+            --build-arg WINDOWS_BASE_IMAGE_TAG `
+            --build-arg GO_VERSION `
+            -t ${{ env.TEST_IMAGE_NAME }} `
+            -f Dockerfile.windows .
       -
         name: Build binaries
         run: |
@@ -152,9 +155,12 @@ jobs:
       -
         name: Build base image
         run: |
-          docker pull ${{ env.WINDOWS_BASE_IMAGE }}:${{ env.WINDOWS_BASE_IMAGE_TAG }}
-          docker tag ${{ env.WINDOWS_BASE_IMAGE }}:${{ env.WINDOWS_BASE_IMAGE_TAG }} microsoft/windowsservercore
-          docker build --build-arg GO_VERSION -t ${{ env.TEST_IMAGE_NAME }} -f Dockerfile.windows .
+          & docker build `
+            --build-arg WINDOWS_BASE_IMAGE `
+            --build-arg WINDOWS_BASE_IMAGE_TAG `
+            --build-arg GO_VERSION `
+            -t ${{ env.TEST_IMAGE_NAME }} `
+            -f Dockerfile.windows .
       -
         name: Test
         run: |
diff --git a/Dockerfile.windows b/Dockerfile.windows
index d3783a9331bf7..93669e698d813 100644
--- a/Dockerfile.windows
+++ b/Dockerfile.windows
@@ -154,13 +154,9 @@
 
 # The number of build steps below are explicitly minimised to improve performance.
 
-# Extremely important - do not change the following line to reference a "specific" image, 
-# such as `mcr.microsoft.com/windows/servercore:ltsc2022`. If using this Dockerfile in process
-# isolated containers, the kernel of the host must match the container image, and hence
-# would fail between Windows Server 2016 (aka RS1) and Windows Server 2019 (aka RS5).
-# It is expected that the image `microsoft/windowsservercore:latest` is present, and matches
-# the hosts kernel version before doing a build. 
-FROM microsoft/windowsservercore
+ARG WINDOWS_BASE_IMAGE=mcr.microsoft.com/windows/servercore
+ARG WINDOWS_BASE_IMAGE_TAG=ltsc2022
+FROM ${WINDOWS_BASE_IMAGE}:${WINDOWS_BASE_IMAGE_TAG}
 
 # Use PowerShell as the default shell
 SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]


Test patch:
diff --git a/testutil/environment/environment.go b/testutil/environment/environment.go
index bef9b58357667..720504b1d27f8 100644
--- a/testutil/environment/environment.go
+++ b/testutil/environment/environment.go
@@ -70,7 +70,7 @@ func getPlatformDefaults(info system.Info) PlatformDefaults {
 			ContainerStoragePath: toSlash(containersPath),
 		}
 	case "windows":
-		baseImage := "microsoft/windowsservercore"
+		baseImage := "mcr.microsoft.com/windows/servercore:ltsc2022"
 		if overrideBaseImage := os.Getenv("WINDOWS_BASE_IMAGE"); overrideBaseImage != "" {
 			baseImage = overrideBaseImage
 			if overrideBaseImageTag := os.Getenv("WINDOWS_BASE_IMAGE_TAG"); overrideBaseImageTag != "" {