Skip to content
Crow CI

Windows Agents

Crow runs Windows jobs on a Windows agent, either a machine you keep running or a VM the autoscaler creates per demand. This page helps you choose how the agent runs those jobs, what that costs, and then covers the docker backend in detail.

ApproachWhat a job getsHost needsUse it for
Docker backend, process isolationA Windows container sharing the host kernel and daemonWindows Server 2022+ with Docker CE; any VM sizeTrusted repositories whose toolchain fits in a Windows container
Docker backend, Hyper-V isolationA Windows container with its own kernel, still on a shared daemonThe above, plus the Hyper-V role and nested virtualizationDefense in depth on a trusted fleet
Hyper-V backendA throwaway Windows VM per workflow, deleted afterwardsThe Hyper-V role, nested virtualization, and a golden imageUntrusted code, or toolchains that need a full Windows install
Local backendA process on the host, with the agent’s accessThe toolchain installed on the hostA single-tenant machine you fully trust
BackendWindowsNotes
docker✅Container steps (incl. clone); see below
hyperv✅One throwaway Windows VM per workflow; see Backend: Hyper-V
local⚠️Works if everything you run is a native Windows executable; see Local backend
kubernetes❌Pods are pinned to nodes with the agent’s OS; scheduling onto Windows nodes is not implemented
podman❌Podman doesn’t run on Windows in the way Crow expects
tart❌Tart only runs macOS and Linux guests on Apple silicon

Crow itself costs nothing extra for Windows. What you pay for is the Windows host, and on a cloud provider the Windows license is part of its hourly price, which roughly doubles it compared to the same machine running Linux.

On-demand prices in USD per hour, as listed by Azure’s and AWS’s pricing APIs in October 2026:

MachinevCPU / RAMLinuxWindowsNested virtualization
Azure Standard_D2s_v5, West Europe2 / 8 GB$0.115$0.207✅
Azure Standard_D4s_v5, West Europe4 / 16 GB$0.230$0.414✅
Azure Standard_D4s_v5, East US4 / 16 GB$0.192$0.376✅
Azure Standard_D4s_v5 spot, East US4 / 16 GB$0.041$0.079✅
Azure Standard_D8s_v5, West Europe8 / 32 GB$0.460$0.828✅
AWS m5.large, Frankfurt2 / 8 GB$0.115$0.207❌
AWS m5.xlarge, Frankfurt4 / 16 GB$0.230$0.414❌
AWS c5.metal, Frankfurt96 / 192 GB$4.656$9.072✅

Prices change and vary by region, so check your provider’s calculator before budgeting. On Azure, a VM the autoscaler creates also has a 127 GB Standard SSD and a public IPv4 address, together about $0.02 per hour; both are deleted with the VM.

Which row applies depends on the approach:

  • Docker backend with process isolation runs on any of them, so the cheapest Windows machine that fits your builds will do.
  • Hyper-V isolation and the Hyper-V backend need nested virtualization. On Azure that is the regular Dv5 and Ev5 sizes; on AWS it means a bare-metal instance, which costs twenty times as much. See Provisioning a Host in the Cloud.

A Windows agent you keep running is billed around the clock: a Standard_D4s_v5 in West Europe comes to about $300 a month.

An autoscaled one is billed only while it exists, which is the job time plus some fixed overhead:

  • Start-up. A fresh Windows VM takes about 8 minutes from creation until its agent registers, including the reboot that installing Hyper-V needs.
  • Golden image. With the Hyper-V backend, a host that starts empty builds its golden image first, which takes about 7 minutes on a Standard_D4s_v5. After that, each workflow’s VM boots in under a minute.
  • Idle time. After its last job a host waits CROW_AGENT_IDLE_TIMEOUT (30 minutes by default) before the autoscaler deletes it, in case more work arrives.

A single Hyper-V backend run with 10 minutes of jobs on an on-demand Standard_D4s_v5 in West Europe therefore costs about 55 minutes, or $0.40. Lowering the idle timeout to 5 minutes brings that to about 30 minutes, or $0.22, at the price of a cold start for the next run; spot pricing brings it to under $0.10. Spot VMs can be evicted at any time, which fails the running workflow, so they suit jobs that are cheap to retry.

On Azure and AWS, the hourly price includes the Windows Server license. Azure’s Windows Server VMs are Datacenter-licensed, which also covers the nested VMs the Hyper-V backend creates.

On your own hardware you need a Windows Server license for the host. Windows Server Standard allows two virtual machines per licensed host, which is why the Hyper-V backend caps itself at two by default; see Licensing.

The rest of this page covers the docker backend unless a section says otherwise.

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 suffix is fixed: there is no option to select a different Windows build, so the host must be able to run ltsc2022 containers.

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.

By default the Windows daemon runs containers with process isolation: every step shares the host kernel, exactly like a Linux container. That is fast, but it means a shared, long-lived daemon runs untrusted pipeline code next to the host kernel, and it requires the image’s OS build to match the host’s.

Hyper-V isolation puts each step in its own lightweight VM with its own kernel. Set it as the agent default:

CROW_BACKEND_DOCKER_ISOLATION=hyperv

or per step:

steps:
  - name: test
    image: mcr.microsoft.com/powershell:nanoserver-ltsc2022
    backend_options:
      docker:
        isolation: hyperv

A step can only strengthen the agent’s setting. On an agent configured with hyperv, a step asking for process or default fails instead of running, so a pipeline cannot opt out of the boundary the operator put in place.

ModeKernelStartupImage/host build match
processshared with the hostfastrequired
hypervown, per containerslowernot required
defaultwhatever the daemon picks--

Isolation is a Windows-only concept. An agent that sets CROW_BACKEND_DOCKER_ISOLATION to process or hyperv while talking to a non-Windows daemon refuses to start, rather than silently running every step in the host kernel.

Hyper-V isolation gives a step its own kernel, but everything around it stays shared and long-lived: one docker daemon serving every repo on the agent, a shared image and layer cache, a shared NAT network, and no per-job disk quota. That is defense in depth for a trusted Windows fleet, not an ephemeral environment for untrusted pull requests.

For that, use the Hyper-V backend, which gives each workflow a throwaway VM and keeps the agent outside it.

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 local backend runs every step as a process on the Windows host itself, with no container in between. It is the right choice when the toolchain cannot run in a Windows container, and the wrong one for anything you do not fully trust: a step has the same access to the host as the agent account. Select it with CROW_BACKEND=local; see Backend: Local for its options.

The step image names the shell that runs the commands, and it has to be on the agent’s PATH:

imageHow the commands run
pwsh or powershellOne PowerShell session with $ErrorActionPreference = "Stop", so the first failing command stops the step
cmdA generated .cmd script that exits on the first non-zero %ERRORLEVEL%
bash, sh, …<shell> -e -c, for a POSIX shell such as the one Git for Windows installs
labels:
  platform: windows/amd64

steps:
  build:
    image: pwsh
    commands:
      - go build ./...
      - go test ./...

A plugin step runs the image name as an executable from PATH instead.

  • git must be installed on the host. The plugin-clone binary is downloaded on first use if it is not on PATH.
  • PowerShell 7 (pwsh) is preferred for the clone step’s credential cleanup, with Windows PowerShell 5.1 as the fallback.
  • Every workflow gets its own home and temp directory. Steps see them as HOME, USERPROFILE, TMP, TEMP and TMPDIR, and cannot override these variables.
  • Cancelling a step terminates the whole process tree it started, not only the shell.

The Crow Autoscaler can provision Windows VMs on demand, on AWS and Azure. 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.
CROW_OS_TYPElinux (the default)windows. On AWS the older CROW_AWS_OS=windows still works and takes precedence when set.

Every Crow release publishes the agent zip, so https://codefloe.com/crowci/crow/releases/download/<version>/crow-agent_windows_amd64.zip works as CROW_AGENT_IMAGE.

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_OS_TYPE: windows
CROW_AGENT_IMAGE: https://codefloe.com/crowci/crow/releases/download/<version>/crow-agent_windows_amd64.zip
CROW_AGENT_ENV: CROW_HEALTHCHECK=false,CROW_AGENT_LABELS=platform=windows/amd64

For Azure, and for a pool running the Hyper-V backend, see Provisioning a Host in the Cloud. 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.

The agent finishes its running workflows before it exits when it receives Ctrl+C, a console close, a logoff or a system shutdown event. docker stop on the Windows agent image triggers the same path. Running the agent directly as a Windows service is not supported: the service control manager’s stop request does not reach it, so a Stop-Service kills the agent mid-workflow. Use a wrapper such as WinSW or NSSM that stops the process with a console control event, or run the container image.