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.

| Backend | Windows | Notes | | ------------ | ------- | ------------------------------------------------------------- | | docker | ✅ | Native git on the host + container steps; see below | | local | ⚠️ | Works if everything you run is a native Windows executable | | kubernetes | ❌ | Pod scheduling onto Windows nodes is not implemented yet | | podman | ❌ | Podman 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.

| 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) | | Volume host path | /var/lib/docker/volumes/<vol>/_data | C:\ProgramData\Docker\volumes\<vol>\_data | | 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) | Native plugin-clone.exe on the host — no Linux container can run | | Host OS requirement | Any Linux distro with Docker CE | Windows Server 2022+ with Docker CE in Windows-container mode |

Two practical consequences fall out of this:

  1. 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.
  2. The Linux clone container can’t run on a Windows daemon. Crow’s standard clone step is a linux/amd64 plugin container, so on Windows the agent runs plugin-clone.exe directly on the host instead, writing into the workflow volume’s host mountpoint so subsequent container steps see the cloned files at the expected workspace path.

Linux Docker can run Linux containers; Windows Docker runs Windows containers. Crow’s clone step is normally a Linux container (crow-plugins/clone), so on Windows it cannot run inside Docker.

Instead, the docker backend routes each step based on the host OS and step type:

| Host OS | Step type | Path | | ------- | --------- | --------------------------------------------------------------- | | Linux | any | Container step via docker run | | Windows | clone | Native plugin-clone.exe against the workflow volume mountpoint | | Windows | other | Container step via docker run |

For Windows clone steps:

  • The agent inspects the workflow volume to find its host mountpoint (e.g. C:\ProgramData\Docker\volumes\<vol>\_data).
  • It downloads plugin-clone.exe to a host cache dir (next to the volume) if not already present.
  • It writes a per-workflow netrc into a temp dir, then spawns plugin-clone.exe against the volume directory.
  • Output streams to the UI via the same TailStep path as container steps.
  • When the process exits, the netrc dir is wiped from disk.

For every other Windows step, Crow uses the normal docker run path with pwsh as the container entrypoint. The agent passes the script payload through $env:CI_SCRIPT and invokes pwsh -NoProfile -NonInteractive -Command "Invoke-Expression(...)" to decode and execute it.

| 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. | | git on PATH | Required for the native clone path. The Git-for-Windows installer is fine; so is scoop install git. | | Network egress | The agent needs outbound HTTPS to your forge, to MCR (mcr.microsoft.com), and to the Crow releases endpoint. |

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.

| 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. |

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:

| 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. | | Provider OS field | linux (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=tier=windows

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-ltsc2022-based images 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 image already present (e.g. with Packer).
  • Run a registry pull-through cache in the same region.

The agent tracks in-flight clone steps in a sync.Map. If the agent process restarts mid-clone, the state is gone and the step must be re-executed. This is acceptable because native clones complete in seconds-to-minutes and the workflow engine handles re-execution.

The clone writes directly into the workflow volume’s host mountpoint. Subsequent container steps mount the same Docker named volume and see the cloned files at the expected workspace path inside the container. The volume is removed by DestroyWorkflow at the end of the workflow.

Native clone runs only on docker and local backends

Section titled “Native clone runs only on docker and local backends”

Only the docker and local backends know how to fall back to plugin-clone.exe on the host. A Windows agent running CROW_BACKEND=kubernetes would try to schedule the Linux clone plugin as a pod and fail. The podman backend isn’t built for Windows at all (//go:build !windows), so CROW_BACKEND=podman won’t start there in the first place.

The plugin-clone.exe binary is downloaded on first use

Section titled “The plugin-clone.exe binary is downloaded on first use”

The first Windows clone on a fresh host downloads plugin-clone.exe from the configured releases URL to a host cache dir. The download is atomic (temp file + rename) and cached for subsequent clones, so this cost is paid once per host. The 10-minute HTTP timeout means a hung releases endpoint won’t pin a workflow indefinitely.