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.
Choosing How to Run Windows Jobs
Section titled “Choosing How to Run Windows Jobs”| Approach | What a job gets | Host needs | Use it for |
|---|---|---|---|
| Docker backend, process isolation | A Windows container sharing the host kernel and daemon | Windows Server 2022+ with Docker CE; any VM size | Trusted repositories whose toolchain fits in a Windows container |
| Docker backend, Hyper-V isolation | A Windows container with its own kernel, still on a shared daemon | The above, plus the Hyper-V role and nested virtualization | Defense in depth on a trusted fleet |
| Hyper-V backend | A throwaway Windows VM per workflow, deleted afterwards | The Hyper-V role, nested virtualization, and a golden image | Untrusted code, or toolchains that need a full Windows install |
| Local backend | A process on the host, with the agent’s access | The toolchain installed on the host | A single-tenant machine you fully trust |
| Backend | Windows | Notes |
|---|---|---|
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 |
What It Costs
Section titled “What It Costs”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:
| Machine | vCPU / RAM | Linux | Windows | Nested virtualization |
|---|---|---|---|---|
Azure Standard_D2s_v5, West Europe | 2 / 8 GB | $0.115 | $0.207 | ✅ |
Azure Standard_D4s_v5, West Europe | 4 / 16 GB | $0.230 | $0.414 | ✅ |
Azure Standard_D4s_v5, East US | 4 / 16 GB | $0.192 | $0.376 | ✅ |
Azure Standard_D4s_v5 spot, East US | 4 / 16 GB | $0.041 | $0.079 | ✅ |
Azure Standard_D8s_v5, West Europe | 8 / 32 GB | $0.460 | $0.828 | ✅ |
AWS m5.large, Frankfurt | 2 / 8 GB | $0.115 | $0.207 | ❌ |
AWS m5.xlarge, Frankfurt | 4 / 16 GB | $0.230 | $0.414 | ❌ |
AWS c5.metal, Frankfurt | 96 / 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.
Always-on versus autoscaled
Section titled “Always-on versus autoscaled”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.
Licensing
Section titled “Licensing”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.
Linux Docker vs Windows Docker
Section titled “Linux Docker vs Windows Docker”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.
| Concern | Linux Docker | Windows Docker (Windows-container mode) |
|---|---|---|
| Containers it can run | Linux containers only | Windows containers only — Linux images fail at docker pull |
| Image manifest tag | linux/amd64, linux/arm64, … | windows/amd64 matching the host build (LTSC2022, etc.) |
| Base image size | Tens of MB (alpine ~5 MB) | Hundreds of MB to multi-GB (nanoserver ~290 MB, servercore ~3 GB) |
| Step entrypoint (Crow) | /bin/sh against the container | pwsh 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 requirement | Any Linux distro with Docker CE | Windows 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).
How It Works
Section titled “How It Works”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 image | Image actually used on Windows |
|---|---|
codefloe.com/crow-plugins/clone:1.1.0 | codefloe.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.
Host Requirements
Section titled “Host Requirements”| Requirement | Notes |
|---|---|
| Windows Server 2022+ | LTSC builds. Earlier versions lack the HCS features Crow relies on. |
| Docker CE | In Windows-container mode (the default on Windows Server). Tested on Docker 25.x. |
| Network egress | The 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.
Isolation
Section titled “Isolation”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:
or per step:
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.
| Mode | Kernel | Startup | Image/host build match |
|---|---|---|---|
process | shared with the host | fast | required |
hyperv | own, per container | slower | not required |
default | whatever 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.
This is not a disposable environment
Section titled “This is not a disposable environment”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.
Recommended Container Images
Section titled “Recommended Container Images”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.
| Image | Works | Approx. size on disk | Notes |
|---|---|---|---|
mcr.microsoft.com/powershell:nanoserver-ltsc2022 | ✅ | ~290 MB | Default. Smallest image with PowerShell 7 on PATH. |
mcr.microsoft.com/powershell:windowsservercore-ltsc2022 | ✅ | ~3 GB | Full Server Core APIs plus both PowerShell editions. |
mcr.microsoft.com/windows/servercore:ltsc2022 | ❌ | ~2 GB | Only ships powershell.exe (5.1), no pwsh. Use the powershell:windowsservercore-* variant instead. |
mcr.microsoft.com/windows/nanoserver:ltsc2022 | ❌ | ~120 MB | Only ships cmd.exe. Use the powershell:nanoserver-* variant instead. |
The clone image must be servercore-based
Section titled “The clone image must be servercore-based”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:
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).
Routing Workflows
Section titled “Routing Workflows”Once you have a mix of Linux and Windows agents, every workflow needs explicit labels so the scheduler picks the right host.
Windows-only workflows
Section titled “Windows-only workflows”Use the built-in platform label:
Or filter per-step with when::
Cross-platform workflows
Section titled “Cross-platform workflows”Linux-only workflows
Section titled “Linux-only workflows”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).
Local backend
Section titled “Local backend”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.
Shell selection
Section titled “Shell selection”The step image names the shell that runs the commands, and it has to be on the agent’s PATH:
image | How the commands run |
|---|---|
pwsh or powershell | One PowerShell session with $ErrorActionPreference = "Stop", so the first failing command stops the step |
cmd | A 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 |
A plugin step runs the image name as an executable from PATH instead.
Requirements and behavior
Section titled “Requirements and behavior”gitmust be installed on the host. Theplugin-clonebinary is downloaded on first use if it is not onPATH.- 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,TEMPandTMPDIR, and cannot override these variables. - Cancelling a step terminates the whole process tree it started, not only the shell.
Autoscaler Integration
Section titled “Autoscaler Integration”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:
| Setting | Linux | Windows |
|---|---|---|
CROW_AGENT_IMAGE | Container image reference | URL to the crow-agent_windows_amd64.zip artifact — the autoscaler downloads it onto the VM. |
CROW_OS_TYPE | linux (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:
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.
Caveats
Section titled “Caveats”First-step image pull is slow
Section titled “First-step image pull is slow”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.
The clone image is always re-pulled
Section titled “The clone image is always re-pulled”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.
Graceful shutdown and Windows services
Section titled “Graceful shutdown and Windows services”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.