# reusable workflow name: .windows # TODO: hide reusable workflow from the UI. Tracked in https://github.com/community/community/discussions/12025 # Default to 'contents: read', which grants actions to read commits. # # If any permission is set, any permission not included in the list is # implicitly set to "none". # # see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions permissions: contents: read on: workflow_call: inputs: os: required: true type: string storage: required: true type: string default: "graphdriver" send_coverage: required: false type: boolean default: false env: GO_VERSION: "1.25.5" GOTESTLIST_VERSION: v0.3.1 TESTSTAT_VERSION: v0.1.25 WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore WINDOWS_BASE_TAG_2022: ltsc2022 WINDOWS_BASE_TAG_2025: ltsc2025 TEST_IMAGE_NAME: moby:test TEST_CTN_NAME: moby DOCKER_BUILDKIT: 0 ITG_CLI_MATRIX_SIZE: 6 jobs: build: runs-on: ${{ inputs.os }} timeout-minutes: 120 # guardrails timeout for the whole job env: GOPATH: ${{ github.workspace }}\go GOBIN: ${{ github.workspace }}\go\bin BIN_OUT: ${{ github.workspace }}\out defaults: run: working-directory: ${{ env.GOPATH }}/src/github.com/docker/docker steps: - name: Checkout uses: actions/checkout@v6 with: path: ${{ env.GOPATH }}/src/github.com/docker/docker - name: Env run: | Get-ChildItem Env: | Out-String - name: Init run: | New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build" New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod" If ("${{ inputs.os }}" -eq "windows-2025") { echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } ElseIf ("${{ inputs.os }}" -eq "windows-2022") { echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } - name: Docker info run: | docker info - name: Build base image run: | & docker build ` --build-arg WINDOWS_BASE_IMAGE ` --build-arg WINDOWS_BASE_IMAGE_TAG ` -t ${{ env.TEST_IMAGE_NAME }} ` -f Dockerfile.windows . - name: Build binaries run: | & docker run --name ${{ env.TEST_CTN_NAME }} -e "DOCKER_GITCOMMIT=${{ github.sha }}" ` ${{ env.TEST_IMAGE_NAME }} hack\make.ps1 -Daemon -Client - name: Copy artifacts run: | New-Item -ItemType "directory" -Path "${{ env.BIN_OUT }}" docker cp "${{ env.TEST_CTN_NAME }}`:c`:\gopath\src\github.com\docker\docker\bundles\docker.exe" ${{ env.BIN_OUT }}\ docker cp "${{ env.TEST_CTN_NAME }}`:c`:\gopath\src\github.com\docker\docker\bundles\dockerd.exe" ${{ env.BIN_OUT }}\ docker cp "${{ env.TEST_CTN_NAME }}`:c`:\gopath\bin\gotestsum.exe" ${{ env.BIN_OUT }}\ docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd.exe" ${{ env.BIN_OUT }}\ docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd-shim-runhcs-v1.exe" ${{ env.BIN_OUT }}\ - name: Upload artifacts uses: actions/upload-artifact@v6 with: name: build-${{ inputs.storage }}-${{ inputs.os }} path: ${{ env.BIN_OUT }}/* if-no-files-found: error retention-days: 2 unit-test: runs-on: ${{ inputs.os }} timeout-minutes: 120 # guardrails timeout for the whole job env: GOPATH: ${{ github.workspace }}\go GOBIN: ${{ github.workspace }}\go\bin defaults: run: working-directory: ${{ env.GOPATH }}/src/github.com/docker/docker steps: - name: Checkout uses: actions/checkout@v6 with: path: ${{ env.GOPATH }}/src/github.com/docker/docker - name: Env run: | Get-ChildItem Env: | Out-String - name: Init run: | New-Item -ItemType "directory" -Path "${{ github.workspace }}\go-build" New-Item -ItemType "directory" -Path "${{ github.workspace }}\go\pkg\mod" New-Item -ItemType "directory" -Path "bundles" If ("${{ inputs.os }}" -eq "windows-2025") { echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } ElseIf ("${{ inputs.os }}" -eq "windows-2022") { echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } - name: Docker info run: | docker info - name: Build base image run: | & docker build ` --build-arg WINDOWS_BASE_IMAGE ` --build-arg WINDOWS_BASE_IMAGE_TAG ` -t ${{ env.TEST_IMAGE_NAME }} ` -f Dockerfile.windows . - name: Test run: | & docker run --name ${{ env.TEST_CTN_NAME }} -e "DOCKER_GITCOMMIT=${{ github.sha }}" ` -v "${{ env.GOPATH }}\src\github.com\docker\docker\bundles:C:\gopath\src\github.com\docker\docker\bundles" ` ${{ env.TEST_IMAGE_NAME }} hack\make.ps1 -TestUnit - name: Send to Codecov if: inputs.send_coverage uses: codecov/codecov-action@v4 with: working-directory: ${{ env.GOPATH }}\src\github.com\docker\docker directory: bundles env_vars: RUNNER_OS flags: unit token: ${{ secrets.CODECOV_TOKEN }} # used to upload coverage reports: https://github.com/moby/buildkit/pull/4660#issue-2142122533 - name: Upload reports if: always() uses: actions/upload-artifact@v6 with: name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\* retention-days: 1 unit-test-report: runs-on: ubuntu-24.04 timeout-minutes: 120 # guardrails timeout for the whole job if: always() needs: - unit-test steps: - name: Set up Go uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} cache: false - name: Download artifacts uses: actions/download-artifact@v7 with: name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports path: /tmp/artifacts - name: Install teststat run: | go install github.com/vearutop/teststat@${{ env.TESTSTAT_VERSION }} - name: Create summary run: | find /tmp/artifacts -type f -name '*-go-test-report.json' -exec teststat -markdown {} \+ >> $GITHUB_STEP_SUMMARY integration-test-prepare: runs-on: ubuntu-24.04 timeout-minutes: 120 # guardrails timeout for the whole job outputs: matrix: ${{ steps.tests.outputs.matrix }} steps: - name: Checkout uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} cache: false - name: Install gotestlist run: go install github.com/crazy-max/gotestlist/cmd/gotestlist@${{ env.GOTESTLIST_VERSION }} - name: Create matrix id: tests working-directory: ./integration-cli run: | # This step creates a matrix for integration-cli tests. Tests suites # are distributed in integration-test job through a matrix. There is # also an override being added to the matrix like "./..." to run # "Test integration" step exclusively. matrix="$(gotestlist -d ${{ env.ITG_CLI_MATRIX_SIZE }} -o "./..." ./...)" echo "matrix=$matrix" >> $GITHUB_OUTPUT - name: Show matrix run: | echo ${{ steps.tests.outputs.matrix }} integration-test: runs-on: ${{ inputs.os }} timeout-minutes: 120 # guardrails timeout for the whole job continue-on-error: ${{ inputs.storage == 'snapshotter' && github.event_name != 'pull_request' }} needs: - build - integration-test-prepare strategy: fail-fast: false matrix: storage: - ${{ inputs.storage }} runtime: - builtin - containerd test: ${{ fromJson(needs.integration-test-prepare.outputs.matrix) }} exclude: - storage: snapshotter runtime: builtin env: GOPATH: ${{ github.workspace }}\go GOBIN: ${{ github.workspace }}\go\bin BIN_OUT: ${{ github.workspace }}\out defaults: run: working-directory: ${{ env.GOPATH }}/src/github.com/docker/docker steps: - name: Checkout uses: actions/checkout@v6 with: path: ${{ env.GOPATH }}/src/github.com/docker/docker - name: Set up Go uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} cache: false - name: Set up OpenTelemetry Collector run: | # The collectors is set up on Linux through the setup-tracing action. If you update the collector here, don't forget to # update the version set in .github/actions/setup-tracing/action.yml. New-Item -ItemType Directory -Force -Path bundles -ErrorAction Continue Start-Process "msiexec" -ArgumentList "/i", "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.140.0/otelcol_0.140.0_windows_x64.msi", "/qn", "/l*v", "$(Join-Path (Get-Location) "bundles/otelcol-install.log")", "COLLECTOR_SVC_ARGS=`"--config=`"`"file:$(Join-Path (Get-Location) "otelcol-ci-config.yml")`"`" --config=`"`"yaml:exporters::file::path: $(Join-Path (Get-Location) "bundles/otel-trace.jsonl")`"`"`"" ` -NoNewWindow -Wait @("OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318", "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf") | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append shell: pwsh - name: Env run: | Get-ChildItem Env: | Out-String - name: Download artifacts uses: actions/download-artifact@v7 with: name: build-${{ inputs.storage }}-${{ inputs.os }} path: ${{ env.BIN_OUT }} - name: Init run: | New-Item -ItemType "directory" -Path "bundles" -ErrorAction SilentlyContinue If ("${{ inputs.os }}" -eq "windows-2025") { echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } ElseIf ("${{ inputs.os }}" -eq "windows-2022") { echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } Write-Output "${{ env.BIN_OUT }}" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append $testName = ([System.BitConverter]::ToString((New-Object System.Security.Cryptography.SHA256Managed).ComputeHash([System.Text.Encoding]::UTF8.GetBytes("${{ matrix.test }}"))) -replace '-').ToLower() echo "TESTREPORTS_NAME=$testName" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append - # removes docker service that is currently installed on the runner. we # could use Uninstall-Package but not yet available on Windows runners. # more info: https://github.com/actions/virtual-environments/blob/d3a5bad25f3b4326c5666bab0011ac7f1beec95e/images/win/scripts/Installers/Install-Docker.ps1#L11 name: Removing current daemon run: | if (Get-Service docker -ErrorAction SilentlyContinue) { $dockerVersion = (docker version -f "{{.Server.Version}}") Write-Host "Current installed Docker version: $dockerVersion" # remove service Stop-Service -Force -Name docker Remove-Service -Name docker # removes event log entry. we could use "Remove-EventLog -LogName -Source docker" # but this cmd is not available atm $ErrorActionPreference = "SilentlyContinue" & reg delete "HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\docker" /f 2>&1 | Out-Null $ErrorActionPreference = "Stop" Write-Host "Service removed" } - name: Starting test daemon run: | Write-Host "Creating service" If ("${{ matrix.runtime }}" -eq "containerd") { $runtimeArg="--default-runtime=io.containerd.runhcs.v1" echo "DOCKER_WINDOWS_CONTAINERD_RUNTIME=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } New-Item -ItemType Directory "$env:TEMP\moby-root" -ErrorAction SilentlyContinue | Out-Null New-Item -ItemType Directory "$env:TEMP\moby-exec" -ErrorAction SilentlyContinue | Out-Null Start-Process -Wait -NoNewWindow "${{ env.BIN_OUT }}\dockerd" ` -ArgumentList $runtimeArg, "--debug", ` "--host=npipe:////./pipe/docker_engine", ` "--data-root=$env:TEMP\moby-root", ` "--exec-root=$env:TEMP\moby-exec", ` "--pidfile=$env:TEMP\docker.pid", ` "--register-service" # Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option. $dockerEnviron = @("DOCKER_MIN_API_VERSION=1.24") $dockerEnviron += @(Get-Item Env:\OTEL_* | ForEach-Object { "$($_.Name)=$($_.Value)" }) If ("${{ inputs.storage }}" -eq "graphdriver") { $dockerEnviron += @("TEST_INTEGRATION_USE_GRAPHDRIVER=1") echo "TEST_INTEGRATION_USE_GRAPHDRIVER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append } New-ItemProperty -Name "Environment" -Path "HKLM:\SYSTEM\CurrentControlSet\Services\docker" -PropertyType MultiString -Value $dockerEnviron Write-Host "Starting service" Start-Service -Name docker Write-Host "Service started successfully!" - name: Waiting for test daemon to start run: | $tries=20 Write-Host "Waiting for the test daemon to start..." While ($true) { $ErrorActionPreference = "SilentlyContinue" & "${{ env.BIN_OUT }}\docker" version $ErrorActionPreference = "Stop" If ($LastExitCode -eq 0) { break } $tries-- If ($tries -le 0) { Throw "Failed to get a response from the daemon" } Write-Host -NoNewline "." Start-Sleep -Seconds 1 } Write-Host "Test daemon started and replied!" If ("${{ matrix.runtime }}" -eq "containerd") { $containerdProcesses = Get-Process -Name containerd -ErrorAction:SilentlyContinue If (-not $containerdProcesses) { Throw "containerd process is not running" } else { foreach ($process in $containerdProcesses) { $processPath = (Get-Process -Id $process.Id -FileVersionInfo).FileName Write-Output "Running containerd instance binary Path: $($processPath)" } } } env: DOCKER_HOST: npipe:////./pipe/docker_engine - name: Docker info run: | & "${{ env.BIN_OUT }}\docker" info env: DOCKER_HOST: npipe:////./pipe/docker_engine - name: Building contrib/busybox run: | & "${{ env.BIN_OUT }}\docker" build -t busybox ` --build-arg WINDOWS_BASE_IMAGE ` --build-arg WINDOWS_BASE_IMAGE_TAG ` .\contrib\busybox\ env: DOCKER_HOST: npipe:////./pipe/docker_engine - name: List images run: | & "${{ env.BIN_OUT }}\docker" images env: DOCKER_HOST: npipe:////./pipe/docker_engine - name: Test integration if: matrix.test == './...' run: | .\hack\make.ps1 -TestIntegration env: DOCKER_HOST: npipe:////./pipe/docker_engine TEST_CLIENT_BINARY: ${{ env.BIN_OUT }}\docker - name: Test integration-cli if: matrix.test != './...' run: | .\hack\make.ps1 -TestIntegrationCli env: DOCKER_HOST: npipe:////./pipe/docker_engine TEST_CLIENT_BINARY: ${{ env.BIN_OUT }}\docker INTEGRATION_TESTRUN: ${{ matrix.test }} - name: Send to Codecov if: inputs.send_coverage uses: codecov/codecov-action@v4 with: working-directory: ${{ env.GOPATH }}\src\github.com\docker\docker directory: bundles env_vars: RUNNER_OS flags: integration,${{ matrix.runtime }} token: ${{ secrets.CODECOV_TOKEN }} # used to upload coverage reports: https://github.com/moby/buildkit/pull/4660#issue-2142122533 - name: Docker info run: | & "${{ env.BIN_OUT }}\docker" info env: DOCKER_HOST: npipe:////./pipe/docker_engine - name: Stop daemon if: always() run: | $ErrorActionPreference = "SilentlyContinue" Stop-Service -Force -Name docker $ErrorActionPreference = "Stop" - # as the daemon is registered as a service we have to check the event # logs against the docker provider. name: Daemon event logs if: always() run: | Get-WinEvent -ea SilentlyContinue ` -FilterHashtable @{ProviderName= "docker"; LogName = "application"} | Sort-Object @{Expression="TimeCreated";Descending=$false} | ForEach-Object {"$($_.TimeCreated.ToUniversalTime().ToString("o")) [$($_.LevelDisplayName)] $($_.Message)"} | Tee-Object -file ".\bundles\daemon.log" - name: Stop OpenTelemetry Collector if: always() run: | (Stop-Service -DisplayName "OpenTelemetry Collector" -PassThru).WaitForStatus('Stopped', (New-TimeSpan -Seconds 30)) - name: Upload reports if: always() uses: actions/upload-artifact@v6 with: name: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-${{ env.TESTREPORTS_NAME }} path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\* retention-days: 1 integration-test-report: runs-on: ubuntu-24.04 timeout-minutes: 120 # guardrails timeout for the whole job continue-on-error: ${{ inputs.storage == 'snapshotter' && github.event_name != 'pull_request' }} if: always() needs: - integration-test strategy: fail-fast: false matrix: storage: - ${{ inputs.storage }} runtime: - builtin - containerd exclude: - storage: snapshotter runtime: builtin steps: - name: Set up Go uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} cache: false - name: Download reports uses: actions/download-artifact@v7 with: path: /tmp/reports pattern: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-* merge-multiple: true - name: Install teststat run: | go install github.com/vearutop/teststat@${{ env.TESTSTAT_VERSION }} - name: Create summary run: | find /tmp/reports -type f -name '*-go-test-report.json' -exec teststat -markdown {} \+ >> $GITHUB_STEP_SUMMARY