Building Windows Executables with GitHub Actions

Introduction

GitHub Actions enables developers to build, test, and package software entirely in the cloud. One particularly powerful capability is the ability to produce native Windows executables (.exe) without using Windows locally.

This article provides a detailed, end-to-end explanation of how that process works. It covers what GitHub Actions actually runs, how Windows binaries are produced, what happens during compilation, and why the resulting executable is indistinguishable from one built on a local Windows machine.

The goal is not just to show how it works, but to explain why it works.


What GitHub Actions Is at a Technical Level

GitHub Actions is a hosted automation and continuous integration platform. When a workflow is triggered, GitHub provisions a runner, which is a temporary virtual machine.

Important characteristics of a runner:

  • Created on demand
  • Fully isolated
  • Preconfigured with common build tools
  • Destroyed immediately after the workflow completes

GitHub provides runners for multiple operating systems:

  • Linux
  • Windows
  • macOS

When a workflow specifies a Windows runner, GitHub spins up a real Windows virtual machine, not an emulator or compatibility layer.


Why Your Local Operating System Does Not Matter

When using GitHub Actions, no compilation happens on your local machine.

Your system is only used to:

  • Edit source code
  • Push commits to GitHub
  • Download build artifacts

All build steps execute inside GitHub’s infrastructure.

Conceptually, the flow looks like this:

Local machine > GitHub repository > GitHub-hosted Windows runner > Windows executable (.exe) > Downloadable artifact

Because the compilation occurs on a Windows system hosted by GitHub, the output is a genuine Windows binary regardless of whether the developer uses Linux, macOS, or another platform locally.


Anatomy of a Typical GitHub Actions Workflow

A simplified workflow for building a Windows executable from a .NET project looks like this:

name: Build

on:
  push:
  workflow_dispatch:

jobs:
  build:
    runs-on: windows-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 6.0.x

      - name: Build
        run: dotnet publish -c Release -r win-x64 --self-contained true

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: bin/Release/net6.0/win-x64/publish/

Each line corresponds to a concrete operation performed on the Windows runner.


Selecting the Windows Build Environment

runs-on: windows-latest

This directive instructs GitHub to allocate a Windows virtual machine.

That machine includes:

  • A Windows kernel and filesystem
  • Native Windows APIs
  • Support for Portable Executable (PE) output
  • Preinstalled system libraries and build utilities

Because the build runs on Windows itself, the generated executable fully conforms to Windows expectations.


Retrieving the Source Code

- uses: actions/checkout@v4

This step clones the repository into the runner’s filesystem.

At this point, the runner has:

  • All source files
  • Project configuration files
  • Dependency definitions
  • Build scripts

The environment mirrors what a developer would see after cloning the repository locally.


Installing the Compiler and Toolchain

- uses: actions/setup-dotnet@v4

This step installs the .NET SDK, which includes:

  • The C# compiler (Roslyn)
  • The .NET CLI (dotnet)
  • Build and publish tooling

This is the same compiler stack used by local development environments such as Visual Studio.


What Actually Happens During the Build

dotnet publish -c Release -r win-x64 --self-contained true

This command performs multiple stages internally.

Compilation to Intermediate Language

  • C# source code is compiled into Intermediate Language (IL)
  • Type information, method signatures, and metadata are generated
  • The result is platform-agnostic managed code

Packaging into a Windows Executable

  • IL and metadata are embedded into a Portable Executable (PE) container
  • The file conforms to the Windows .exe format
  • Entry points and headers are generated according to Windows standards

Runtime Bundling

Because --self-contained true is specified:

  • The .NET runtime is bundled with the executable
  • The output does not depend on a preinstalled framework
  • The executable is portable across compatible Windows systems

The final output is a standalone Windows binary.


Artifact Storage and Retrieval

- uses: actions/upload-artifact@v4

Artifacts are files preserved after the workflow finishes.

Key properties:

  • Stored by GitHub after the runner is destroyed
  • Downloadable via the GitHub web interface or API
  • Immutable once uploaded

Artifacts are the only persistent output of the workflow, as the runner itself is ephemeral.


This Is Not Cross-Compilation

This process is often misunderstood as cross-compilation, but it is not.

Cross-compilation would involve:

  • A non-Windows system producing Windows binaries directly

In contrast, GitHub Actions:

  • Runs the build on a native Windows system
  • Uses Windows toolchains
  • Produces binaries exactly as a local Windows machine would

The developer is delegating compilation to a remote Windows environment, not translating binaries across platforms.


Determinism and Clean Build Environments

Because each runner is:

  • Freshly provisioned
  • Free of local configuration drift
  • Destroyed after use

Builds are:

  • Highly reproducible
  • Consistent across runs
  • Isolated from developer-specific environments

This eliminates many common sources of build inconsistency.


Mental Model to Remember

GitHub Actions can be understood as:

A temporary, disposable Windows machine that builds your software and disappears, leaving only the results behind.


Conclusion

GitHub Actions enables reliable production of Windows executables without requiring local Windows infrastructure. By leveraging hosted Windows runners and official toolchains, it provides:

  • Native Windows builds
  • Reproducible results
  • Clean, isolated environments
  • Minimal local setup

Understanding this model clarifies why the generated executables behave exactly like those built on a local Windows system and why the developer’s local platform is irrelevant to the final output.