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
- Connect to the server where the application will be hosted (or local machine).
- 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')) - 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 :
to verify installed packages.choco list -lo
Step 3: Set Up a GitHub Self-Hosted Runner
- Go to your GitHub repository.
- Navigate to: Settings > Actions > Runners
- Click "New self-hosted runner" and follow the instructions:
- Select your operating system and architecture
- Run the provided shell commands on your server
- (Optional) Install as a Windows service for persistence
-
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
- Open IIS Manager (inetmgr).
- Add a new website with:
- Site Name: MyApp
- Physical Path:
C:\inetpub\wwwroot\MyApp - Port: 80 (or a custom port)
-
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! 👇