Skip to content
Crow CI

Windows Agents

Crow can execute pipelines on Windows hosts using the Docker backend in Windows-container mode. This page covers the architecture, host requirements, recommended images, and known caveats.

BackendWindowsNotes
dockerContainer steps (incl. clone); see below
local⚠️Works if everything you run is a native Windows executable
kubernetesPod scheduling onto Windows nodes is not implemented yet
podmanPodman doesn’t run on Windows in the way Crow expects

The rest of this page assumes the docker backend.

Docker on Linux and Docker on Windows share a CLI but run different container runtimes with different constraints. Understanding these differences explains most of the Windows-specific behavior on this page.

ConcernLinux DockerWindows Docker (Windows-container mode)
Containers it can runLinux containers onlyWindows containers only — Linux images fail at docker pull
Image manifest taglinux/amd64, linux/arm64, …windows/amd64 matching the host build (LTSC2022, etc.)
Base image sizeTens of MB (alpine ~5 MB)Hundreds of MB to multi-GB (nanoserver ~290 MB, servercore ~3 GB)
Step entrypoint (Crow)/bin/sh against the containerpwsh directly — cmd.exe and Windows PowerShell 5.1 don’t work as entrypoint
Clone step (Crow)Linux clone plugin container (crow-plugins/clone)Windows variant of the clone image (…-windows-ltsc2022)
Host OS requirementAny Linux distro with Docker CEWindows Server 2022+ with Docker CE in Windows-container mode

The key consequence: image manifests don’t cross OS boundaries. A Linux image manifest cannot be pulled on a Windows daemon, and vice versa. This is why every workflow in a mixed pool needs explicit labels: platform: — the scheduler must not put a Linux job on a Windows agent or it will fail at docker pull. It is also why the clone step needs a Windows-specific image (see below).

Every step on a Windows agent — including the clone — runs as a normal Windows container via docker run. Crow’s standard clone image (crow-plugins/clone) is published as a Linux image, which cannot run on a Windows daemon, so a separate Windows image is published with a -windows-ltsc2022 tag suffix.

On a Windows agent the docker backend automatically rewrites the clone step’s image tag to that Windows variant and always re-pulls it:

Configured clone imageImage actually used on Windows
codefloe.com/crow-plugins/clone:1.1.0codefloe.com/crow-plugins/clone:1.1.0-windows-ltsc2022

The rewrite appends -windows-ltsc2022 to the existing tag (a digest-pinned reference is left unchanged) and forces a pull, so a reused agent VM never runs a stale cached image.

The clone runs in the container with the workflow volume mounted at the workspace path, so subsequent steps (which mount the same named volume) see the cloned source — standard Docker volume sharing, identical to Linux.

RequirementNotes
Windows Server 2022+LTSC builds. Earlier versions lack the HCS features Crow relies on.
Docker CEIn Windows-container mode (the default on Windows Server). Tested on Docker 25.x.
Network egressThe agent needs outbound HTTPS to your forge, to MCR (mcr.microsoft.com), and to your container registry.

git is not required on the host — cloning happens inside the clone container.

AWS’s Windows_Server-2022-English-Full-ECS_Optimized AMI matches all of these out of the box (Docker 25.x pre-installed) and is the recommended starting point if you’re using the autoscaler.

Step images must have pwsh (PowerShell 7+) on PATH. Crow invokes pwsh directly as the container entrypoint to sidestep cmd.exe’s argument-quoting quirks, which silently corrupt our launcher when interleaved with Docker’s standard argv escaping. Windows PowerShell 5.1 (powershell.exe) is not supported as an entrypoint.

ImageWorksApprox. size on diskNotes
mcr.microsoft.com/powershell:nanoserver-ltsc2022~290 MBDefault. Smallest image with PowerShell 7 on PATH.
mcr.microsoft.com/powershell:windowsservercore-ltsc2022~3 GBFull Server Core APIs plus both PowerShell editions.
mcr.microsoft.com/windows/servercore:ltsc2022~2 GBOnly ships powershell.exe (5.1), no pwsh. Use the powershell:windowsservercore-* variant instead.
mcr.microsoft.com/windows/nanoserver:ltsc2022~120 MBOnly ships cmd.exe. Use the powershell:nanoserver-* variant instead.

git submodule is the one git operation that shells out to a POSIX helper run via the bundled MSYS2 sh.exe. That shell cannot start on nanoserver (it fails with STATUS_ENTRYPOINT_NOT_FOUND because nanoserver lacks Win32 APIs the MSYS2 runtime needs). The published Windows clone image is therefore servercore-based. This only affects the clone image — your step images can still be nanoserver-based as long as they don’t run git submodule themselves.

Building Windows container images in pipelines

Section titled “Building Windows container images in pipelines”

Windows images must be built on a Windows host, and the Docker buildx multi-arch flow used for Linux doesn’t apply. The pattern is docker-out-of-docker: a step mounts the host Docker daemon’s named pipe and runs docker build/docker push:

labels:
  platform: windows/amd64

steps:
  build-image:
    image: mcr.microsoft.com/powershell:windowsservercore-ltsc2022
    volumes:
      - '\\.\pipe\docker_engine:\\.\pipe\docker_engine'
    commands:
      - docker build -f Containerfile.windows -t my/image:tag .
      - docker push my/image:tag

The build context is sent to the daemon by the in-container Docker client, so a normal . context works as long as the workspace is populated (i.e. the clone succeeded).

Once you have a mix of Linux and Windows agents, every workflow needs explicit labels so the scheduler picks the right host.

Use the built-in platform label:

labels:
  platform: windows/amd64

steps:
  build:
    image: mcr.microsoft.com/powershell:nanoserver-ltsc2022
    commands:
      - $PSVersionTable.PSVersion

Or filter per-step with when::

steps:
  windows-tests:
    image: mcr.microsoft.com/powershell:nanoserver-ltsc2022
    commands:
      - pwsh ./run-windows-tests.ps1
    when:
      - platform: windows/amd64
when:
  - platform: ['linux/*', 'windows/amd64']

If a Windows agent is in the pool, every existing Linux workflow must also have explicit labels — otherwise the scheduler may put a Linux-only job onto a Windows agent and it will fail at docker pull (Linux image manifest on a Windows daemon).

labels:
  platform: linux/*
# or pin to a specific agent:
labels:
  agent: my-linux-host

The Crow Autoscaler can provision Windows VMs on demand. The configuration shape is identical to a Linux autoscaler with two key differences:

SettingLinuxWindows
CROW_AGENT_IMAGEContainer image referenceURL to the crow-agent_windows_amd64.zip artifact — the autoscaler downloads it onto the VM.
Provider OS fieldlinux (provider-default)Set the provider’s Windows flag (e.g. CROW_AWS_OS=windows).

Minimal AWS Windows-autoscaler config:

CROW_PROVIDER: aws
CROW_AWS_REGION: eu-central-1
CROW_AWS_INSTANCE_TYPE: m5.large
CROW_AWS_AMI_ID: <Windows Server 2022 ECS-Optimized AMI>
CROW_AWS_OS: windows
CROW_AGENT_IMAGE: https://example.com/releases/crow-agent_windows_amd64.zip
CROW_AGENT_ENV: CROW_HEALTHCHECK=false,CROW_AGENT_LABELS=platform=windows/amd64

See Autoscaler configuration for the full surface.

A fresh Windows VM has Docker installed but no images pre-pulled. The first step that uses mcr.microsoft.com/powershell:nanoserver-ltsc2022 will pull ~290 MB; windowsservercore-based images (including the clone image) can take several minutes to pull on first use.

Pull progress goes to the agent’s stdout, not the step output in the UI, so the workflow may appear to hang. Subsequent runs on the same VM are instant.

Mitigations:

  • Pre-pull common images in user-data when provisioning the VM.
  • Bake a custom AMI with the images already present (e.g. with Packer).
  • Run a registry pull-through cache in the same region.

Because the Windows clone tag is mutable, the agent forces a pull of the clone image on every clone step so a reused VM can’t run a stale copy. This adds the (cached after first use) pull cost per fresh VM.