diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5b496bb..c271fe2 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -2,19 +2,24 @@ name: CI
 on:
   push:
     branches: [ main, master ]
+    tags:
+      - v*
   pull_request:
     branches: [ main, master ]
   workflow_dispatch:
 
 jobs:
   build:
+    permissions:
+      contents: write
+      packages: write
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Set up Go
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v4
         with:
-          go-version: 1.21
+          go-version-file: 'go.mod'
 
       # local build
       - name: Compile the binary
@@ -28,26 +33,51 @@ jobs:
           make build
           docker run `make version` --help
 
+      - name: Login to DockerHub
+        uses: docker/login-action@v3
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+        with:
+          username: ${{ secrets.DOCKERHUB_USER }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
       # multi-arch build
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v1
+        uses: docker/setup-qemu-action@v3
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v1
-      - name: Build regular image
-        id: docker_build
-        uses: docker/build-push-action@v2
+        uses: docker/setup-buildx-action@v3
+
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
         with:
-          context: .
-          file: ./Dockerfile
-          platforms: linux/amd64,linux/arm64
-          target: simple
-      - name: Build vendor image
-        id: docker_build_vendor
-        uses: docker/build-push-action@v2
+          bake-target: docker-metadata-action
+          images: |
+            ${{ github.repository_owner }}/goldpinger
+          tags: |
+            type=ref,event=branch
+            type=ref,event=pr
+            type=semver,pattern={{version}}
+          labels: |
+            org.opencontainers.image.title=${{ github.repository }}
+            org.opencontainers.image.description=Goldpinger makes calls between its instances to monitor your networking. It runs as a DaemonSet on Kubernetes and produces Prometheus metrics that can be scraped, visualised and alerted on.
+            org.opencontainers.image.vendor=${{ github.repository_owner }}
+
+      - name: Build regular image
+        uses: docker/bake-action@v4
         with:
-          context: .
-          flavor: |
-            suffix: -vendor,onlatest=false
-          file: ./Dockerfile
-          platforms: linux/amd64,linux/arm64
-          target: vendor
+          targets: ci
+          push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
+          files: |
+            ./docker-bake.hcl
+            ${{ steps.meta.outputs.bake-file }}
+
+      # https://github.com/docker/buildx/issues/2105
+      - name: Create manifest
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+        run: |
+          set -xe
+          for image in $images; do
+            docker buildx imagetools create -t "${image}" "${image}-linux" "${image}-windows-ltsc2019" "${image}-windows-ltsc2022"
+          done
+        env:
+          images: "${{ join( steps.meta.outputs.tags, ' ') }}"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
deleted file mode 100644
index c7a5202..0000000
--- a/.github/workflows/publish.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-name: Publish
-
-on:
-  push:
-    # Publish `v*` tags as releases.
-    tags:
-    - v*
-  pull_request:
-
-jobs:
-  publish:
-    runs-on: ubuntu-latest
-    if: github.event_name == 'push'
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v2
-      - name: Set up QEMU
-        uses: docker/setup-qemu-action@v1
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v1
-      - name: Login to DockerHub
-        uses: docker/login-action@v1
-        if: github.event_name != 'pull_request'
-        with:
-          username: ${{ secrets.DOCKERHUB_USER }}
-          password: ${{ secrets.DOCKERHUB_TOKEN  }}
-      - name: Docker meta
-        id: meta
-        uses: docker/metadata-action@v3
-        with:
-          images: bloomberg/goldpinger
-      - name: Build and push
-        id: docker_build
-        uses: docker/build-push-action@v2
-        with:
-          context: .
-          file: ./Dockerfile
-          platforms: linux/amd64,linux/arm64
-          push: ${{ github.event_name != 'pull_request' }}
-          tags: ${{ steps.meta.outputs.tags }}
-          labels: ${{ steps.meta.outputs.labels }}
-          target: simple
-      - name: Build and push vendor
-        id: docker_build_vendor
-        uses: docker/build-push-action@v2
-        with:
-          context: .
-          flavor: |
-            suffix: -vendor,onlatest=false
-          file: ./Dockerfile
-          platforms: linux/amd64,linux/arm64
-          push: ${{ github.event_name != 'pull_request' }}
-          tags: ${{ steps.meta.outputs.tags }}-vendor
-          labels: ${{ steps.meta.outputs.labels }}
-          target: vendor
diff --git a/Dockerfile b/Dockerfile
index 23bda17..9b7f5e1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,6 @@
-FROM golang:1.21 as builder
+ARG WINDOWS_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:ltcs2022
+
+FROM --platform=$BUILDPLATFORM golang:1.21 as builder
 ARG TARGETARCH
 ARG TARGETOS
 
@@ -20,6 +22,12 @@ COPY ./static /static
 COPY ./config /config
 ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]
 
+FROM $WINDOWS_BASE_IMAGE AS windows
+COPY --from=builder /w/bin/goldpinger /goldpinger.exe
+COPY ./static /static
+COPY ./config /config
+ENTRYPOINT ["/goldpinger.exe", "--static-file-path=/static"]
+
 # For vendor builds, use the simple build and add the vendor'd files
 FROM simple as vendor
 COPY --from=builder /w/vendor /goldpinger-vendor-sources
diff --git a/docker-bake.hcl b/docker-bake.hcl
new file mode 100644
index 0000000..fa4447a
--- /dev/null
+++ b/docker-bake.hcl
@@ -0,0 +1,52 @@
+# ref: https://docs.docker.com/build/bake/reference/
+
+# ref: https://github.com/docker/metadata-action?tab=readme-ov-file#bake-definition
+target "docker-metadata-action" {
+  tags = ["goldpinger:latest"]
+}
+
+group "default" {
+  targets = ["linux-simple"]
+}
+
+group "ci" {
+  targets = ["linux-simple", "linux-vendor", "windows-nanoserver-ltsc2019", "windows-nanoserver-ltsc2022"]
+}
+
+target "linux-simple" {
+  inherits = ["docker-metadata-action"]
+  tags = "${formatlist("%s-linux", target.docker-metadata-action.tags)}"
+  platforms = ["linux/amd64", "linux/arm64"]
+  target = "simple"
+}
+
+target "linux-vendor" {
+  inherits = ["docker-metadata-action"]
+  tags = "${formatlist("%s-vendor", target.docker-metadata-action.tags)}"
+  platforms = ["linux/amd64", "linux/arm64"]
+  target = "vendor"
+}
+
+target "windows-nanoserver-ltsc2019" {
+  inherits = ["docker-metadata-action"]
+  tags = "${formatlist("%s-windows-ltsc2019", target.docker-metadata-action.tags)}"
+
+  platforms = ["windows/amd64"]
+
+  target = "windows"
+  args = {
+    WINDOWS_BASE_IMAGE = "mcr.microsoft.com/windows/nanoserver:ltsc2019"
+  }
+}
+
+target "windows-nanoserver-ltsc2022" {
+  inherits = ["docker-metadata-action"]
+  tags = "${formatlist("%s-windows-ltsc2022", target.docker-metadata-action.tags)}"
+
+  platforms = ["windows/amd64"]
+
+  target = "windows"
+  args = {
+    WINDOWS_BASE_IMAGE = "mcr.microsoft.com/windows/nanoserver:ltsc2022"
+  }
+}