Skip to content

Commit bd38b03

Browse files
authored
Merge pull request #2 from AnastaZIuk/buildkit
Docker -> Buildkit + Containerd for Windows runners
2 parents c435f37 + 3868219 commit bd38b03

File tree

5 files changed

+293
-50
lines changed

5 files changed

+293
-50
lines changed

.github/workflows/main.yml

Lines changed: 143 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,61 +14,161 @@ jobs:
1414
steps:
1515
- name: Checkout Repository
1616
uses: actions/checkout@v4
17-
18-
- name: Restore Cached Image TAR
19-
uses: actions/cache@v4
2017
with:
21-
path: cache-image.tar
22-
key: docker-image-${{ runner.os }}-${{ github.sha }}
23-
restore-keys: |
24-
docker-image-${{ runner.os }}-
25-
26-
- name: Load Cached Image
27-
shell: pwsh
28-
id: load_cache
29-
run: |
30-
if (Test-Path cache-image.tar) {
31-
docker load -i cache-image.tar
32-
echo "CACHE_HIT=true" >> $env:GITHUB_ENV
33-
}
18+
fetch-depth: 1
3419

35-
- name: Build Image Without Cache
36-
if: env.CACHE_HIT != 'true'
20+
- name: Setup environment
3721
shell: pwsh
3822
run: |
39-
docker build -t app:latest .
40-
41-
- name: Save Docker Image to TAR
42-
if: env.CACHE_HIT != 'true'
43-
shell: pwsh
44-
run: |
45-
docker save -o cache-image.tar app:latest
46-
47-
- name: Cache Image TAR
48-
if: env.CACHE_HIT != 'true'
49-
uses: actions/cache@v4
23+
Set-MpPreference -DisableRealtimeMonitoring $true
24+
- name: Restore BuildKit Cache
25+
uses: actions/cache/restore@v4
26+
id: restore-buildkit-cache
27+
with:
28+
path: |
29+
buildkit
30+
key: buildkit-${{ runner.os }}-
31+
restore-keys: |
32+
buildkit-${{ runner.os }}-
33+
- name: Restore Daemon Cache
34+
uses: actions/cache/restore@v4
35+
id: restore-droot-cache
5036
with:
51-
path: cache-image.tar
52-
key: docker-image-${{ runner.os }}-${{ github.sha }}
37+
path: |
38+
droot
39+
key: droot-${{ runner.os }}-
5340
restore-keys: |
54-
docker-image-${{ runner.os }}-
41+
droot-${{ runner.os }}-
42+
- name: Log in to Container Registry
43+
uses: docker/login-action@v3
44+
with:
45+
registry: dr-private.devsh.eu
46+
username: ${{ secrets.DOCKER_USERNAME }}
47+
password: ${{ secrets.DOCKER_PASSWORD }}
5548

56-
- name: Run Nano Container
49+
- name: Setup buildkit
5750
shell: pwsh
5851
run: |
59-
docker run -di --name orphan -v "${{ github.workspace }}:C:\app" app:latest
60-
61-
- name: Nano Container - Configure CMake Project
52+
if (-Not (Test-Path "buildkit")) {
53+
Write-Host "📂 'buildkit' directory not found. Downloading and extracting daemons..."
54+
New-Item -ItemType Directory -Path "buildkit" -Force
55+
Invoke-WebRequest -Uri "https://github.com/moby/buildkit/releases/download/v0.20.1/buildkit-v0.20.1.windows-amd64.tar.gz" -OutFile "buildkit.tar.gz"
56+
tar -xf buildkit.tar.gz -C buildkit
57+
58+
Invoke-WebRequest -Uri "https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-windows-amd64.tar.gz" -OutFile "containerd.tar.gz"
59+
tar -xf containerd.tar.gz -C buildkit
60+
} else {
61+
Write-Host "✅ 'buildkit' directory already exists. Skipping download."
62+
}
63+
.\cni\setup-nat.ps1
64+
New-Item -ItemType Directory -Path "buildkit/logs" -Force
65+
$Bin = "${{ github.workspace }}\buildkit\bin"
66+
echo "PATH=$Bin;$env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
67+
- name: Run daemons
6268
shell: pwsh
69+
run: |
70+
function Wait-For {
71+
param (
72+
[string]$Command,
73+
[int]$TickWait = 1,
74+
[int]$TotalTicks = 5
75+
)
76+
77+
Write-Host "⏳ Waiting for command.. (Max retries: $TotalTicks, Interval: ${TickWait}s)"
78+
79+
for ($i=1; $i -le $TotalTicks; $i++) {
80+
Write-Host "⏳ [$i/$TotalTicks]: $Command"
81+
if (Invoke-Expression $Command) {
82+
Write-Host "✅ Success!"
83+
return $true
84+
}
85+
Start-Sleep -Seconds $TickWait
86+
}
87+
88+
Write-Error "❌ Timeout: $Command did not succeed!"
89+
exit 1
90+
}
91+
$logs = "buildkit/logs"
92+
$cni = "${{ github.workspace }}\buildkit\cni"
93+
$droot = "${{ github.workspace }}\droot"
94+
Start-Job -ScriptBlock {
95+
param($logs, $droot)
96+
Start-Process containerd.exe `
97+
-ArgumentList @("--root=$droot\containerd") `
98+
-RedirectStandardOutput "$logs/containerd.log" `
99+
-RedirectStandardError "$logs/containerd_error.log" `
100+
-NoNewWindow -PassThru
101+
} -ArgumentList $logs, $droot
102+
Wait-For "ctr --namespace buildkit image ls"
103+
Start-Job -ScriptBlock {
104+
param($logs, $cni, $droot)
105+
Start-Process buildkitd.exe `
106+
-ArgumentList @("--containerd-cni-config-path=$cni\0-containerd-nat.conf",
107+
"--containerd-cni-binary-dir=$cni", `
108+
"--root=$droot\buildkitd") `
109+
-RedirectStandardOutput "$logs/buildkitd.log" `
110+
-RedirectStandardError "$logs/buildkitd_error.log" `
111+
-NoNewWindow -PassThru
112+
} -ArgumentList $logs, $cni, $droot
113+
Wait-For "buildctl du"
114+
- name: Build base image
115+
shell: cmd
63116
run: |
64-
docker exec -w "C:\app\tests" -i orphan cmd /c cmake --preset configure-msvc-winsdk
65-
66-
- name: Nano Container - Build the Project
117+
buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. ^
118+
--output=type=image,name=dr-private.devsh.eu/actions/cache/nano/base,push=true ^
119+
--export-cache=type=inline ^
120+
--import-cache=type=registry,ref=dr-private.devsh.eu/actions/cache/nano/base ^
121+
--opt target=buildtools
122+
- name: Prune images
67123
shell: pwsh
68124
run: |
69-
docker exec -w "C:\app\tests" -i orphan cmd /c cmake --build --preset build-msvc-winsdk --config Release
70-
71-
- name: Nano Container - Test the Project
125+
buildctl prune
126+
- name: Stop daemons
72127
shell: pwsh
73128
run: |
74-
docker exec -w "C:\app\tests\build\src" -i orphan cmd /c ctest -C Release --stop-on-failure --verbose
129+
Write-Host "🔴 Stopping background jobs..."
130+
if (Get-Job) {
131+
Get-Job | Stop-Job -Force
132+
Get-Job | Remove-Job -Force
133+
Write-Host "✅ Background jobs stopped!"
134+
} else {
135+
Write-Host "⚠️ No background jobs found."
136+
}
137+
Write-Host "🔴 Stopping containerd and buildkitd..."
138+
if (Get-Process -Name "containerd" -ErrorAction SilentlyContinue) {
139+
Stop-Process -Name "containerd" -Force
140+
Write-Host "✅ containerd stopped!"
141+
} else {
142+
Write-Host "⚠️ containerd is not running."
143+
}
144+
145+
if (Get-Process -Name "buildkitd" -ErrorAction SilentlyContinue) {
146+
Stop-Process -Name "buildkitd" -Force
147+
Write-Host "✅ buildkitd stopped!"
148+
} else {
149+
Write-Host "⚠️ buildkitd is not running."
150+
}
151+
#- name: Build app image
152+
# shell: cmd
153+
# run: |
154+
# buildctl build --frontend=dockerfile.v0 --local context=. --local dockerfile=. ^
155+
# --output type=image,name=nano/app,push=false ^
156+
# --export-cache type=local,dest=buildkit/.cache,mode=max ^
157+
# --import-cache type=local,src=buildkit/.cache ^
158+
# --opt target=nano
159+
160+
- name: Save BuildKit Cache
161+
id: save-buildkit-cache
162+
uses: actions/cache/save@v4
163+
with:
164+
path: |
165+
buildkit
166+
key: buildkit-${{ runner.os }}-
167+
168+
- name: Save Daemon Cache
169+
id: save-droot-cache
170+
uses: actions/cache/save@v4
171+
with:
172+
path: |
173+
droot
174+
key: droot-${{ runner.os }}-

Dockerfile

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# escape=`
22

3-
FROM --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 AS BUILD_TOOLS
3+
FROM mcr.microsoft.com/windows/servercore:ltsc2022 AS buildtools
44

55
ARG WINDOWS_11_SDK_VERSION="22621"
66
ARG VC_VERSION="14.42.17.12"
@@ -25,7 +25,9 @@ echo "Error: Expected MSVC version directory %MSVC_VERSION% does not exist!" &&
2525

2626
RUN (echo { "WINDOWS_SDK_VERSION": "10.0.%WINDOWS_11_SDK_VERSION%.0", "VC_VERSION": "%VC_VERSION%", "MSVC_VERSION": "%MSVC_VERSION%" }) > %BUILD_TOOLS_DIR%\env.json
2727

28-
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
28+
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022 AS nano
29+
30+
SHELL ["cmd", "/S", "/C"]
2931

3032
ARG BUILD_TOOLS_DIR="C:\BuildTools"
3133
ARG WINDOWS_KITS_10_DIR="C:\WindowsKits10SDK"
@@ -38,9 +40,6 @@ ARG NINJA_DIR="C:\Ninja"
3840
ARG NASM_VERSION=2.16.03
3941
ARG NASM_DIR="C:\Nasm"
4042

41-
COPY --link --from=BUILD_TOOLS ["C:/Program Files (x86)/Windows Kits/10", "${WINDOWS_KITS_10_DIR}"]
42-
COPY --link --from=BUILD_TOOLS ["C:/artifacts", "${BUILD_TOOLS_DIR}"]
43-
4443
ENV CMAKE_WINDOWS_KITS_10_DIR=${WINDOWS_KITS_10_DIR} BUILD_TOOLS_DIR=${BUILD_TOOLS_DIR} `
4544
CMAKE_VERSION=${CMAKE_VERSION} CMAKE_URL=https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-windows-x86_64.zip CMAKE_DIR=${CMAKE_DIR} `
4645
PYTHON_VERSION=${PYTHON_VERSION} PYTHON_URL=https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-embed-amd64.zip PYTHON_DIR=${PYTHON_DIR} `
@@ -52,10 +51,11 @@ RUN cd C:\Temp && mkdir "%PYTHON_DIR%" && curl -SL --output python.zip %PYTHON_U
5251
RUN cd C:\Temp && mkdir "%NINJA_DIR%" && curl -SL --output ninja.zip %NINJA_URL% && tar -xf ninja.zip -C "%NINJA_DIR%" && del ninja.zip
5352
RUN cd C:\Temp && mkdir "%NASM_DIR%" && curl -SL --output nasm.zip %NASM_URL% && tar -xf nasm.zip -C "%NASM_DIR%" && del nasm.zip
5453

54+
RUN echo "test"
55+
COPY --from=buildtools ["C:/artifacts", "${BUILD_TOOLS_DIR}"]
5556
RUN cd %BUILD_TOOLS_DIR% && "%PYTHON_DIR%\python.exe" -c "import json, os; env=json.load(open('./env.json')); [os.system(f'setx {k} \"{v}\"') for k,v in env.items()]" `
5657
&& setx PATH "%CMAKE_DIR%\cmake-%CMAKE_VERSION%-windows-x86_64\bin;%PYTHON_DIR%;%NINJA_DIR%;%NASM_DIR%\nasm-%NASM_VERSION%;%PATH%" `
5758
&& setx MSVC_TOOLSET_DIR "%BUILD_TOOLS_DIR%\VC\Tools\MSVC\%MSVC_VERSION%"
59+
COPY --from=buildtools ["C:/Program Files (x86)/Windows Kits/10", "${WINDOWS_KITS_10_DIR}"]
5860

59-
SHELL ["cmd", "/c"]
6061
ENTRYPOINT ["cmd"]
61-
CMD ["/noexit"]

cni/setup-nat.ps1

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# https://github.com/moby/buildkit/blob/248ff7c29ed979ef0c142b8166dd6d192cf0b215/docs/windows.md#cni--networking-setup
2+
$workspaceDir = Resolve-Path "$PSScriptRoot\.."
3+
$networkName = 'nat'
4+
$natInfo = Get-HnsNetwork -ErrorAction Ignore | Where-Object { $_.Name -eq $networkName }
5+
if ($null -eq $natInfo) {
6+
throw "NAT network not found, check if you enabled containers, Hyper-V features and restarted the machine"
7+
}
8+
$gateway = $natInfo.Subnets[0].GatewayAddress
9+
$subnet = $natInfo.Subnets[0].AddressPrefix
10+
11+
$cniVersion = "1.0.0"
12+
$cniPluginVersion = "0.3.1"
13+
$cniBinDir = "$workspaceDir\buildkit\cni"
14+
$cniConfPath = "$cniBinDir\0-containerd-nat.conf"
15+
16+
if (-Not (Test-Path "$cniConfPath")) {
17+
Write-Host "📂 'cni' directory not found. Downloading and extracting cni binaries..."
18+
19+
mkdir $cniBinDir -Force
20+
curl.exe -LO https://github.com/microsoft/windows-container-networking/releases/download/v$cniPluginVersion/windows-container-networking-cni-amd64-v$cniPluginVersion.zip
21+
Expand-Archive -Path windows-container-networking-cni-amd64-v$cniPluginVersion.zip -DestinationPath $cniBinDir -Force
22+
} else {
23+
Write-Host "✅ 'cni' directory already exists. Skipping download."
24+
}
25+
26+
$natConfig = @"
27+
{
28+
"cniVersion": "$cniVersion",
29+
"name": "$networkName",
30+
"type": "nat",
31+
"master": "Ethernet",
32+
"ipam": {
33+
"subnet": "$subnet",
34+
"routes": [
35+
{
36+
"gateway": "$gateway"
37+
}
38+
]
39+
},
40+
"capabilities": {
41+
"portMappings": true,
42+
"dns": true
43+
}
44+
}
45+
"@
46+
Set-Content -Path $cniConfPath -Value $natConfig
47+
cat $cniConfPath

wsl/setup-wsl.ps1

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
param (
2+
[string]$WslMsiUrl = "https://github.com/microsoft/WSL/releases/download/2.4.12/wsl.2.4.12.0.x64.msi",
3+
[string]$AlpineZipUrl = "https://github.com/yuk7/AlpineWSL/releases/download/3.21.3-0/Alpine.zip",
4+
[string]$LinuxKernelbzImageUrl = "https://github.com/Devsh-Graphics-Programming/WSL2-Linux-Kernel/releases/download/wsl2-kernel-13885376672/bzImage",
5+
[string]$RegistryTag = "2.8.3"
6+
)
7+
8+
$WorkspaceDir = Resolve-Path "$PSScriptRoot\.."
9+
$LinuxDir = "$WorkspaceDir\linux"
10+
if (!(Test-Path $LinuxDir)) { New-Item -ItemType Directory -Path $LinuxDir | Out-Null }
11+
12+
$bzImagePath = "$LinuxDir\bzImage"
13+
$ext4Path = "$LinuxDir\ext4.vhdx"
14+
$wslInstaller = "$LinuxDir\wsl.msi"
15+
16+
$bzImageCached = Test-Path $bzImagePath
17+
$ext4Cached = Test-Path $ext4Path
18+
$wslCached = Test-Path $wslInstaller
19+
20+
if ($bzImageCached -and $ext4Cached -and $wslCached) {
21+
echo "REGISTRY_CACHE_HIT=true" >> $env:GITHUB_ENV
22+
}
23+
24+
if (-not $wslCached) {
25+
Write-Host "$wslInstaller not found in cache, downloading..."
26+
Invoke-WebRequest -Uri $WslMsiUrl -OutFile $wslInstaller
27+
} else {
28+
Write-Host "$wslInstaller found in cache, skipping download."
29+
}
30+
31+
Write-Host "Updating WSL..."
32+
Start-Process -Wait "msiexec.exe" -ArgumentList "/i `"$wslInstaller`" /qn /norestart" -NoNewWindow -PassThru
33+
34+
wsl --set-default-version 2
35+
36+
Write-Host "Updating .wslconfig..."
37+
$wslConfigPath = "$env:USERPROFILE\.wslconfig"
38+
$kernelPath = $bzImagePath.Replace("\", "\\")
39+
$wslConfigContent = @"
40+
[wsl2]
41+
kernel=$kernelPath
42+
"@
43+
$wslConfigContent | Set-Content -Path $wslConfigPath -Encoding UTF8 -Force
44+
45+
Write-Host "==== .wslconfig CONTENT ====" -ForegroundColor Green
46+
Write-Host $wslConfigContent -ForegroundColor Green
47+
48+
if (-not $bzImageCached) {
49+
Write-Host "$bzImagePath not found in cache, downloading..."
50+
Invoke-WebRequest -Uri $LinuxKernelbzImageUrl -OutFile $bzImagePath
51+
} else {
52+
Write-Host "$bzImagePath found in cache, skipping download."
53+
}
54+
55+
if (-not $ext4Cached) {
56+
Write-Host "$ext4Path not found in cache, creating Alpine instance with Docker registry..."
57+
Invoke-WebRequest -Uri $AlpineZipUrl -OutFile "$WorkspaceDir\Alpine.zip"
58+
Expand-Archive -Path "$WorkspaceDir\Alpine.zip" -DestinationPath $LinuxDir -Force
59+
Write-Output "`n" | & "$LinuxDir\Alpine.exe"
60+
wsl -s Alpine
61+
wsl apk add docker
62+
wsl nohup dockerd > /dockerd-log 2>&1 &
63+
& "$WorkspaceDir\wsl\wait-for-daemon.ps1"
64+
wsl docker pull registry:$RegistryTag
65+
} else {
66+
Write-Host "$ext4Path found in cache, skipping download & importing!"
67+
wsl --import-in-place Alpine "$ext4Path"
68+
wsl -s Alpine
69+
}
70+
71+
Write-Host "==== LINUX WSL DIRECTORY STRUCTURE ====" -ForegroundColor Cyan
72+
Get-ChildItem -Path $LinuxDir -Recurse | ForEach-Object { $_.FullName.Replace($PWD.Path, "").Replace("\", "/") }

wsl/wait-for-daemon.ps1

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
param (
2+
[int]$maxRetries = 15,
3+
[int]$delaySeconds = 1
4+
)
5+
6+
$linuxDockerReady = $false
7+
$i = 0
8+
9+
while ($i -lt $maxRetries) {
10+
Write-Host "[$i/$maxRetries]: Waiting for Linux Docker daemon..."
11+
Start-Sleep -Seconds $delaySeconds
12+
wsl docker info > $null 2>&1
13+
if ($LASTEXITCODE -eq 0) {
14+
Write-Host "Linux Docker daemon running!" -ForegroundColor Green
15+
$linuxDockerReady = $true
16+
break
17+
}
18+
$i++
19+
}
20+
21+
if (-not $linuxDockerReady) {
22+
Write-Host "Failed to connect to Linux Docker daemon!" -ForegroundColor Red
23+
exit 1
24+
}

0 commit comments

Comments
 (0)