Setting Up CI/CD for .NET Web Applications Using GitHub Actions and Self-Hosted Windows Runners

Posted by Brahim Salim on June 9, 2025

Modern development workflows demand automation, consistency, and flexibility—especially when deploying .NET applications to on-premises infrastructure. While cloud-based CI/CD solutions (like GitHub-hosted runners) work well for public-facing apps, many enterprises require self-hosted runners due to security policies, compliance, or performance needs. This guide provides a step-by-step approach to setting up a self-hosted Windows runner with GitHub Actions, automating builds, and deploying to IIS—ideal for teams managing internal .NET web applications.

Why Use a Self-Hosted Runner?

GitHub-hosted runners are convenient, but self-hosted runners offer:

  • Full control over the build environment
  • Faster builds (no queue times)
  • Access to internal servers (IIS, databases, on-prem resources)
  • Compliance with strict security policies

Step 1: Install Chocolatey on the Server

Chocolatey simplifies package management on Windows.

Installation Steps

  1. Connect to the server where the application will be hosted (or local machine).
  2. Open PowerShell as Administrator and run:
    Set-ExecutionPolicy Bypass -Scope Process -Force;
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;
    iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
  3. Verify installation:
    choco -v

Troubleshooting Tip:

If Chocolatey fails to install, check:

  • Proxy settings (if behind a corporate firewall).
  • PowerShell execution policy (must allow scripts).

Step 2: Install Required Build Tools

Since we’re building a .NET app, we need:

  • Visual Studio Build Tools (for MSBuild)
  • .NET SDK (if not already installed)

Install Build Tools via Chocolatey

choco install visualstudio2022buildtools `
--params "'--add Microsoft.VisualStudio.Workload.WebBuildTools --quiet --norestart'" -y

For .NET 6+, also run:

choco install dotnet-sdk -y

Pro Tip:

  • Check the Chocolatey packages for specific versions.
  • Use :
    choco list -lo
    to verify installed packages.

Step 3: Set Up a GitHub Self-Hosted Runner

  1. Go to your GitHub repository.
  2. Navigate to: Settings > Actions > Runners
  3. Click "New self-hosted runner" and follow the instructions:
  4. Select your operating system and architecture
  5. Run the provided shell commands on your server
  6. (Optional) Install as a Windows service for persistence
  7. Once completed, you should see:
    √ Connected to GitHub
                              Listening for Jobs...

Verify Runner Status

  • Check GitHub Settings → Actions → Runners for a green status.
  • Test with a simple workflow.

Security Best Practices:

  • Store the runner token in GitHub Secrets.
  • Run the service under a least-privilege account.

Step 4: Configure IIS for Hosting the Application

  1. Open IIS Manager (inetmgr).
  2. Add a new website with:
    • Site Name: MyApp
    • Physical Path: C:\inetpub\wwwroot\MyApp
    • Port: 80 (or a custom port)
  3. Grant permissions:
    icacls "C:\inetpub\wwwroot\MyApp" /grant "IIS_IUSRS:(OI)(CI)(F)"

Important:

  • If using HTTPS, bind a certificate.
  • Ensure the runner service account has write access to the deployment folder.

Sample GitHub Actions Workflow (Full CI/CD Pipeline)

Start by creating a .github/workflows/filename.yml file. This file:

  • runs on every `push` to `main`
  • builds the project
  • Backs up the old deployment
  • Deploys to IIS
name: .NET CI/CD to IIS

on:
  push:
    branches: [ main ]

jobs:
  build-deploy:
    runs-on: self-hosted  # Uses your Windows runner

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

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

      - name: Restore NuGet Packages
        run: dotnet restore MyApp.sln

      - name: Backup Old Deployment (PowerShell)
        shell: powershell
        run: |
          $DeployPath = "C:\inetpub\wwwroot\MyApp"
          $BackupPath = "C:\inetpub\wwwroot\Backups"
          $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
          $BackupFolder = "$BackupPath\MyApp_$Timestamp"

          if (!(Test-Path $BackupPath)) { New-Item -Path $BackupPath -ItemType Directory }

          Write-Host "Stopping IIS..."
          iisreset /stop

          if (Test-Path $DeployPath) {
              Move-Item -Path $DeployPath -Destination $BackupFolder
              Write-Host "Backup saved to: $BackupFolder"
          }

          New-Item -Path $DeployPath -ItemType Directory | Out-Null

      - name: Build & Publish
        run: dotnet publish MyApp/MyApp.csproj -c Release -o $DeployPath

      - name: Restart IIS
        run: iisreset /start

Managing Secrets Securely

Never hardcode credentials! Use GitHub Secrets:

env:
  DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
  DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }}

Conclusion

Using GitHub Actions with a self-hosted Windows runner provides a powerful, flexible pipeline for deploying .NET apps in environments where cloud runners don’t fit — like internal servers.

This setup gives you:

  • Full control of the environment
  • Seamless integration with GitHub
  • Automation across build, test, and deployment stages

Next Steps

  • Add Slack/Teams notifications for deployment status.
  • Implement deployment gates (approvals for production).
  • Explore Docker-based deployments for consistency.

Need Help?

If you run into issues, check:

💬 Questions or feedback? Let me know by send message! 👇