Run Hyper-V workloads on Windows runners with nested virtualization

Run Hyper-V workloads on Windows runners with nested virtualization

RunsOn can launch Windows x64 runners with nested virtualization enabled, which is useful when a workflow needs Hyper-V itself: Hyper-V isolated Windows containers, VM smoke tests, image tooling, or other virtualization-heavy checks.

The important part is the /nested-virt label on a nested-capable x64 instance family:

jobs:
  windows-nested-virt:
    runs-on: runs-on=${{ github.run_id }}/family=m8i.large/volume=100gb:gp3:750mbs:4000iops/image=windows25-full-x64/nested-virt

    steps:
      - name: Verify Hyper-V on Windows
        shell: pwsh
        run: |
          $ErrorActionPreference = "Stop"

          $computer = Get-CimInstance -ClassName Win32_ComputerSystem
          if ($computer.HypervisorPresent -ne $true) {
            throw "Expected HypervisorPresent=True"
          }

          foreach ($featureName in @("Hyper-V", "Hyper-V-PowerShell", "Containers")) {
            $feature = Get-WindowsFeature -Name $featureName
            if ($feature.InstallState -ne "Installed") {
              throw "Expected $featureName to be installed, got $($feature.InstallState)"
            }
          }

          Import-Module Hyper-V -ErrorAction Stop

          docker run --rm --isolation=hyperv `
            mcr.microsoft.com/windows/servercore:ltsc2025 `
            cmd /c ver

          $vmName = "runs-on-nested-virt-smoke-$([guid]::NewGuid().ToString('N').Substring(0,8))"
          try {
            New-VM -Name $vmName -Generation 2 -MemoryStartupBytes 512MB -NoVHD | Out-Null
            Start-VM -Name $vmName -ErrorAction Stop | Out-Null
            Start-Sleep -Seconds 5

            $state = (Get-VM -Name $vmName).State
            if ($state -ne "Running") {
              throw "Expected nested VM to reach Running state, got $state"
            }
          }
          finally {
            if (Get-VM -Name $vmName -ErrorAction SilentlyContinue) {
              Stop-VM -Name $vmName -TurnOff -Force -ErrorAction SilentlyContinue | Out-Null
              Remove-VM -Name $vmName -Force -ErrorAction SilentlyContinue
            }
          }

A few notes:

  • Use a Windows x64 image.
  • Pick an instance family that supports nested virtualization. m8i.large is a good default for simple checks.
  • Nested virtualization is opt-in. Without /nested-virt, the normal Windows launch template is used.

Last updated: April 28, 2026

View original discussion