I have found a way to build Windows container images without Docker - It doesn't use a Dockerfile though.
The way I got this to work is using crane from Google (https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md) which lets you append image layers to a base image. You can build Windows images on either Linux or Windows or inside of a container (Linux/Windows) - however, you'll need to build any artefacts (e.g. EXE, DLLs) on a Windows machine since this approach is using Crane to assemble a Windows container image on a Linux host (container).
Here is a quick PoC I did to prove it works (using Docker Desktop with Windows Container mode on my laptop):
dotnet --version
# 7.0.102
dotnet new mvc --output demoapp
cd demoapp/
dotnet publish -c release -o ./app
dotnet run
curl http://localhost:5190
# CTRL+C
# Create app image layer
tar -cf layer.tar app/
# Install the crane binary (Windows version)
curl -L https://github.com/google/go-containerregistry/releases/download/v0.12.1/go-containerregistry_Windows_x86_64.tar.gz -o go-containerregistry.tar.gz
tar -zxvf go-containerregistry.tar.gz -C C:\Windows\system32 crane.exe
del go-containerregistry.tar.gz
# Assemble the final image by appending the app layer to the base layer
# If you omit -o, the image is pushed to the remote registry at -t <repository> so you need
# to have docker credentials; or use krane to leverage managed identity - haven't tested with Azure Kubernetes Service (AKS) and Azure Container Registry (ACR)
crane append ^
--platform=windows/amd64 ^
-f layer.tar ^
-t demoapp:0.1.0 ^
-b mcr.microsoft.com/dotnet/aspnet:7.0-windowsservercore-ltsc2022 ^
-o demoapp.tar
dir demoapp.tar
# ...snip...
xx/xx/xxxx xx:xx AM 1,396,281,856 demoapp.tar
#...snip...
tar tvf demoapp.tar
#-rw-r--r-- 0 0 0 3216 Jan 01 1970 sha256:d576bfe48a1f2884038e5876e98c6756cb1dbeb73b1bc289b1361ad6d839a95c
#-rw-r--r-- 0 0 0 1347458067 Jan 01 1970 6a0972e4a21b1e21e65ee97fd7f7b548b42f85db2bcb8c941555d9a7111d7e22.tar.gz
#-rw-r--r-- 0 0 0 1301 Jan 01 1970 796faebe10adce59dd1ce19cdd905bf8cd57b731534448fe726a2a441a3b76ca.tar.gz
#-rw-r--r-- 0 0 0 34182392 Jan 01 1970 657125dd0de011d4a46d561aebe39b951f7482a22696b177e02bdb6808b569e5.tar.gz
#-rw-r--r-- 0 0 0 333137 Jan 01 1970 4eaf08bdfb873a65e058ff99bce0e75fedec1a513a1a67f1aec993d1e7a6b6f0.tar.gz
#-rw-r--r-- 0 0 0 1297 Jan 01 1970 d050f03ea7b1d03f7cd2eb1dda9476017e7f539a8710df08e3204a1987fc429d.tar.gz
#-rw-r--r-- 0 0 0 12102725 Jan 01 1970 efb292e52cf975caf059e235b2a05e85ba0522d9c6663888a74493bbd0d4641d.tar.gz
#-rw-r--r-- 0 0 0 2190007 Jan 01 1970 54c2a964360adb1915d39b28d24165a20ed00b9362401a2ca79a005d170ea5b9.tar.gz
#-rw-r--r-- 0 0 0 1047 Jan 01 1970 manifest.json
docker load -i demoapp.tar
#docker load -i demoapp.tar
#c3156b72235f: Loading layer [==================================================>] 2.19MB/2.19MB
#Loaded image: demoapp:0.1.0
docker run --rm -it -p 5000:80 --workdir c:\app demoapp:0.1.0 dotnet .\demoapp.dll
curl -i http://localhost:5000
#HTTP/1.1 200 OK
#Content-Type: text/html; charset=utf-8
#Date: Tue, 07 Feb 2023 00:27:58 GMT
#Server: Kestrel
#Transfer-Encoding: chunked
#
#<!DOCTYPE html>
#...snip...
Summary:
- I published a release of my app (.NET 7 on Windows OS host)
- Created a tarball of the output dir (./app)
- Used crane to append the layer onto the base image (aspnet:7.0-windowsservercore-ltsc2022)
- Stored resulting image as a tarball on disk (can also push it directly with crane to a registry)
- Loaded into load Docker engine
- Run the image in Docker windows to verify it works
A couple of things I haven't figure out how to do:
- Update manifest to set workdir and cmd/entrypoint -- crane supports the mutate sub-command to set entrypoint and cmd, but I can't see how to update the workdir
- Integrate with ACR -- shouldn't be hard with admin user (docker login) but I haven't tested it with managed identity on AKS.
Extensions:
- Mount a volume as a scratch pad for builds -- clean up afterwards
- Install AzDo or Github Actions runner agent inside container to run builds from a CI pipeline
- Grant Kube API access using a service account and role bindings on the pod so app builds can be launched outside the container (kubectl run) and then copy the artefacts back using (kubectl cp) -- probably overkill and not sure if its going to work seamlessly
Anyway, I just wanted to prove it was possible.
Some references that were useful:
- https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md
- https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_append.md
- https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_mutate.md
- https://www.phillipsj.net/posts/creating-windows-container-images-without-docker/