Build Environments standard
Standard scope
Applies to: All HCS-governed repos, across all scopes — build hosts invoked directly, not CI-registered runners Domain: build-environments Status: Active
This standard defines how HCS projects provision and use build environments — hosts you invoke directly, by name, outside of CI dispatch. It does not cover the registered CI runner fleet (GitHub Actions and GitLab CI self-hosted agents); that fleet is governed by CI Runners. See that standard's "Runner vs. build environment" section for the distinction if you're unsure which one applies to your case.
Build environment priority order
Always use the highest-priority available option. Do not reach for a Windows VM when the work runs fine on Linux.
| Priority | Environment | When to use | Cost |
|---|---|---|---|
| 1 — WSL (local VM) | Ubuntu 22.04 on the platform VM | Linux builds, scripts, containers — whenever the work is not Windows-only | Free |
| 2 — bld-01 (nested Hyper-V) | Windows Server 2025, 10.10.10.10, this host only | Windows-only work: WinPE ISOs, PowerShell modules, ADK builds | Free (shared) |
| 3 — Quick-deploy Windows server (fallback) | Ephemeral Windows VM | Tier 2 unavailable (off-host session or bld-01 unusable); Windows-only work only | VM cost while running |
| 4 — AWS EC2 Mac | Active on demand in ProdTech | iOS/macOS development and builds | See EC2 Mac runbook |
Decision rule: if the work does not explicitly require Windows APIs, ADK, COM objects, or Apple tooling, it runs on Tier 1. Reserve Tier 2 and Tier 3 for true Windows-only builds. Tier 4 is the Apple-only path and is provisioned only for approved work windows.
Tier 1 — WSL (Ubuntu 22.04 on the platform VM)
Use directly on the platform VM host. No VM startup, no cost.
# Run a Linux build directly from PowerShell on the host
wsl --distribution Ubuntu-22.04 -- bash -c "cd /mnt/d/git/myrepo && make build"Works for: bash scripts, make, cargo, go build, docker buildx, npm/pnpm, Python, az CLI.
Tier 2 — bld-01 (nested Hyper-V Windows build box)
Access details
| Property | Value |
|---|---|
| VM name | bld-01 |
| OS | Windows Server 2025 Standard (Desktop Experience) |
| vCPU | 4 |
| RAM | Dynamic 2–16 GB |
| VHDX | D:\Data\VMs\bld-01.vhdx |
| Switch | beacon-nat (Internal NAT, 10.10.10.0/24) |
| IP | 10.10.10.10 (static) |
| Credentials | Administrator / kv-hcs-vault-01 → hcs-bld-vm-password |
| Host only | This specific platform VM (nested Hyper-V) |
Power management is required. bld-01 is powered off when idle. Every build script must power it on, run the build, and restore the original power state. Use the standard wrapper below.
When to use Tier 2
- WinPE ISO generation (Windows ADK)
- PowerShell modules that use Windows-only APIs
- .NET Framework (not .NET 8+ Core) builds
- COM/WMI/Windows-specific testing
For anything that works on Linux, use Tier 1 instead.
Standard build wrapper
The power-on/run/power-off wrapper is a template script, not something to inline in each repo's own scripts: D:\git\platform\templates\scripts\Invoke-BuildOnVm.ps1. It verifies bld-01 exists (deploying it if not), records its power state, powers it on and waits for WinRM if needed, runs the supplied script block or file over PS Remoting, then restores the original power state.
# Copy the template into your repo, then call it:
.\scripts\Invoke-BuildOnVm.ps1 -ScriptBlock { cd C:\build\myrepo; git pull; .\build.ps1 }
.\scripts\Invoke-BuildOnVm.ps1 -ScriptFile .\scripts\build.ps1Tier 3 — Quick-deploy Windows build server (fallback)
When bld-01 is unavailable (off-host session, VHDX corruption, different machine), deploy an ephemeral Windows VM:
| Property | Value |
|---|---|
| OS | Windows Server 2025 |
| Credentials | Administrator / kv-hcs-vault-01 → hcs-winbuild-vm-password |
| Deploy script | D:\git\platform\.ado\cicd\Deploy-CicdPlatform.ps1 -DeployWindowsRunner |
| Lifecycle | Ephemeral — deallocate or delete after the build completes |
# Deploy the fallback VM and run a build
D:\git\platform\.ado\cicd\Deploy-CicdPlatform.ps1 -DeployWindowsRunner -ScriptFile scripts\build.ps1Tier 4 — AWS EC2 Mac (active on demand)
The reusable foundation and the first ProdTech M4 host are deployed. This tier provides an interactive Xcode environment and can optionally become a trusted registered CI runner. The full host lifecycle, cost rules, and runner trust boundary live in CI Runners.
Access follows this chain:
- The operator signs in through AWS IAM Identity Center with temporary credentials.
- AWS Systems Manager establishes SSH or Screen Sharing tunnels; no inbound management port is exposed publicly.
- The EC2 Mac uses an IAM instance profile for AWS API access.
- Apple signing material is retrieved from the scope's approved vault only when required and is never baked into an AMI.
The SSO directory Region is not the Mac deployment Region. Deployment automation must choose the Mac Region and Availability Zone from live offerings, quotas, capacity, and price.
For the deployed ProdTech host, connection commands, the IAM Identity Center assignment, current Region, RDM setup, Key Vault secret names, lifecycle controls, and cost boundaries are in the AWS EC2 Mac build-host runbook. Agents connected to the HCS Governance MCP can retrieve the same material with get_guidance(topic="aws-ec2-mac") or guidance://aws-ec2-mac.
Why this standard exists
Azure VM run-command (V1 and V2) has critical limitations for build workloads:
- 90-minute hard timeout — any build longer than 90 minutes is killed
- Extension deadlocks — only one run-command can execute at a time; a stuck extension can block a VM for hours
- No shell access — without RDP or WinRM in the network security group, there is no escape hatch when a command hangs
- Provisioning state coupling — a VM stuck in
Updatingblocks all ARM (Azure Resource Manager) operations on it
Local Hyper-V (Tier 2) eliminates all of these. WSL (Tier 1) eliminates them at zero cost for Linux builds.
Architecture overview
Platform VM (Azure WS2025)
├── WSL (Tier 1) — Ubuntu 22.04, Linux-native builds, no startup cost
├── Hyper-V (Tier 2)
│ └── bld-01 — Windows Server 2025, WinPE/ADK/PS modules
│ NAT switch: beacon-nat 10.10.10.0/24 → host .1, guest .10
└── Windows fallback VM (Tier 3 — ephemeral, deployed on demand only)
AWS (Tier 4 — active on demand)
└── EC2 Mac M4 in us-west-2 (family and Region revalidated before allocation)
└── SSM-tunneled SSH and Screen Sharing; no public inbound rulesDeploying bld-01 (first time or recovery)
The deployment script: D:\git\platform\scripts\build\Deploy-BuildVm.ps1
Steps:
- Creates the
beacon-natinternal switch (idempotent) - Creates the NAT rule (idempotent)
- Creates a dynamic 127 GB VHDX at
D:\Data\VMs\bld-01.vhdx - Applies the WS 2025 WIM (index 2) from
D:\Data\images\ - Injects a valid Unattend.xml (≤15-char computer name, admin credentials, WinRM)
- Creates the Gen 2 Hyper-V VM
- Boots and waits for OOBE (Out-of-Box Experience) to complete
One-time cost: 20–40 minutes on first deploy.
Prerequisites
| Requirement | Check |
|---|---|
| Hyper-V installed | Get-WindowsFeature Hyper-V |
| WS 2025 ISO present | D:\Data\images\en-us_windows_server_2025_*.iso |
D:\Data\VMs\ directory | Created by deploy script |
kv-hcs-vault-01 accessible | az keyvault secret show ... |
Critical: Windows computer names must be ≤ 15 characters (NetBIOS limit). If the name is longer, Setup silently rejects the Unattend.xml and OOBE blocks indefinitely.
Build tool matrix
| Tool | Tier 1 WSL | Tier 2 bld-01 (Windows) |
|---|---|---|
| WinPE ISO (ADK) | No | Yes |
| PowerShell modules | No | Yes |
| .NET / C# | Yes | Yes |
| Node.js / TypeScript | Yes | Yes |
| Azure Bicep | Yes | Yes |
| Terraform | Yes | Yes |
| Docker images | Yes | Yes (nested) |
| Linux-only tools | Yes | No |
| Bash scripts | Yes | No |
For build tools running on the CI runner fleet (GitHub Actions / GitLab CI agents), see CI Runners.
Repo integration guide (Tier 2 only)
Only repos with genuine Windows-only builds need these steps. Most repos never touch a build environment directly — their CI runs on the runner fleet (see CI Runners).
1. Add the wrapper script
# In your repo: scripts/Invoke-BuildOnVm.ps1
# Copy from: D:\git\platform\templates\scripts\Invoke-BuildOnVm.ps12. Reference in CI/CD
ADO pipeline:
- task: PowerShell@2
displayName: Build on bld-01
inputs:
filePath: scripts/Invoke-BuildOnVm.ps1
arguments: -ScriptFile scripts/build.ps1
pwsh: trueGitHub Actions (calling out to bld-01 from a step running on the platform VM):
- name: Build on bld-01
shell: pwsh
run: scripts/Invoke-BuildOnVm.ps1 -ScriptFile scripts/build.ps13. Write your build script
Your repo's scripts/build.ps1 runs inside bld-01. It can assume:
- PowerShell 7 at
C:\Program Files\PowerShell\7\pwsh.exe - Git at
C:\Program Files\Git\cmd\git.exe - Azure CLI at
az - Windows ADK + WinPE at
C:\Program Files (x86)\Windows Kits\10\ - .NET SDK at
C:\dotnet\
MCP integration
This standard is served by the HCS Governance MCP (Model Context Protocol):
Tool: get_standard
Domain: build-environmentsAny AI client connected to the MCP can retrieve this document for build-host selection guidance. For runner-label guidance, the same client should call get_standard(domain="ci-runners") instead.
Related standards
- Scripting — PowerShell 7 conventions used in build scripts
- Automation — pipeline-first principle; pipeline design lives there, compute selection does not
- CI Runners — the registered CI runner fleet (GitHub Actions / GitLab CI self-hosted agents); use this instead of this standard when the question is "what runner label goes in my YAML"
- Infrastructure — IaC for build VM provisioning
- Key Vault — build credentials belong in the consuming scope's assigned vault;
kv-hcs-vault-01is only the HCS-scope default
Checklist: onboarding a repo's build environment
This checklist covers Tier 2 (bld-01) onboarding only. For registering a repo with the CI runner fleet (GitHub Actions / GitLab CI labels and tags), use the checklist in CI Runners instead — that's a separate step from anything below.
- [ ] Copy
Invoke-BuildOnVm.ps1to the repo'sscripts/folder - [ ] Write
scripts/build.ps1with repo-specific build steps - [ ] Add a CLAUDE.md entry: "Windows builds run on bld-01 via
Invoke-BuildOnVm.ps1" - [ ] Add
kv-hcs-vault-01access to the repo's CI service principal - [ ] Validate that
bld-01has all tools the build needs - [ ] Test:
Invoke-BuildOnVm -ScriptBlock { "UP:$env:COMPUTERNAME" }