From Zero to Hero: Setting Up CI/CD with GitHub Actions, Including Your Localhost Server
13 Mar 2025 · Rabington Chitima
Most times, we unknowingly short-change ourselves by avoiding experiences that could set us apart. But here's the thing: hands-on experience with CI/CD is a game-changer. By the end of this guide, not only will you be comfortable discussing continuous integration and deployment, but you'll also have a practical setup running on your own machine — lowering the barrier to entry into the DevOps ecosystem.
For the bigger chunk of my career, Azure DevOps was my go-to tool for pipelines. It's powerful, enterprise-ready, and widely used in corporate environments. But when I started setting up a CI/CD pipeline for my personal projects, GitHub Actions felt more streamlined and well-integrated — especially for repositories already on GitHub. And let's be honest, it's quickly becoming the default for developers working on side projects or open-source contributions.
Who Is This For?
If you've ever found yourself:
- Copying published files manually from your computer to another machine
- Deploying directly to a folder (e.g., wwwroot for IIS)
- Physically carrying files to a client's environment on a USB stick
- Manually uploading builds to cloud platforms like AWS, Azure, or DigitalOcean
- SFTP-ing files to a remote server after every update
Then it's time to automate! In this guide, I'll walk you through how I:
- Set up a self-hosted GitHub Actions runner on my Windows 11 machine
- Automated builds and deployments to IIS
- Created a streamlined CI/CD pipeline for local and remote hosting
Let's get started!
Step 1: Setting Up the Self-Hosted Runner
I have an old Windows 11 PC which now works as my dedicated Homelab for a development server and hosts personal stuff. I digress — you don't need 2 computers, this can work using the same machine you develop on.
Since my development server was also my deployment target, I needed a self-hosted runner (GitHub also provides cloud-hosted runners).
Install the GitHub Self-Hosted Runner
- Navigate to your GitHub repository → Settings → Actions → Runners.
- Click New self-hosted runner (Windows for me), and follow the instructions.
- Or, run the following commands in PowerShell to create and navigate to the
actions-runnerfolder in your preferred location:
mkdir actions-runner
cd actions-runner
Download and extract runner files:
Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v2.309.0/actions-runner-win-x64-2.309.0.zip -OutFile actions-runner-win-x64-2.309.0.zip
Expand-Archive -Path actions-runner-win-x64-2.309.0.zip -DestinationPath .
Configure the Runner — replace with your details from GitHub (Repo → Settings → Actions → Runners):
.\config.cmd --url https://github.com/YOUR_USERNAME/YOUR_REPO --token YOUR_TOKEN
Start the runner (you can also set it to run as a service so it starts on boot):
.\run.cmd
At this point, my runner was connected, and GitHub could now trigger workflows on my machine. Not necessary for this exercise, but you can also set Secrets and Variables here for things like tokens and DB connections.
Step 2: Preparing IIS for Deployment
For a .NET Core app, the quicker option is IIS:
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServer
Then configure IIS: open IIS Manager → Add Website, set the physical path to the folder where the app will be deployed, and assign a port (e.g. 8080).
Step 3: Writing the GitHub Actions Workflow
With the server ready, it was time to automate deployments. I created .github/workflows/deploy.yml in my repository and split the steps into two jobs, Build and Deploy:
name: CI/CD UmbracoSite to IIS
on:
push:
branches:
# git branch you want to trigger the deployment
- master
jobs:
build: # First Job
# Ensure this points to your self-hosted runner
runs-on: self-hosted
# If you have set up any Secrets and Variables this is how you can get them
env:
DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
# Add .NET to PATH for current session
- name: Manually Add Default .NET Path to GitHub PATH
shell: powershell
run: |
echo "C:\dotnet" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8
# Install .NET SDK (if not already installed in C:\dotnet)
- name: Install .NET SDK if not already installed
shell: powershell
run: |
if (-Not (Test-Path "C:\dotnet\dotnet.exe")) {
Write-Host ".NET SDK not found in C:\dotnet. Installing..."
Invoke-WebRequest -Uri https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1
.\dotnet-install.ps1 -InstallDir "C:\dotnet" -Version 8.0.406
[System.Environment]::SetEnvironmentVariable("DOTNET_ROOT", "C:\dotnet", "Machine")
[System.Environment]::SetEnvironmentVariable("Path", "C:\dotnet;$([System.Environment]::GetEnvironmentVariable('Path', 'Machine'))", "Machine")
} else {
Write-Host ".NET SDK already installed in C:\dotnet"
}
- name: Verify .NET Installation
shell: powershell
run: dotnet --version
- name: Restore dependencies
run: dotnet restore
# Publish the Project
- name: Build & Publish
run: dotnet publish -c Release -o ./publish
- name: Upload Artifact # we will download this on deployment
uses: actions/upload-artifact@v4
with:
name: umbraco-published
path: ./publish
deploy: # Second job
runs-on: self-hosted # Runs on the IIS server, same as above
steps:
- name: Download Artifact # the uploaded files from the previous step
uses: actions/download-artifact@v4
with:
name: umbraco-published
path: C:\inetpub\wwwroot\Umbraco
# Restart IIS to Apply Changes
- name: Restart IIS
shell: powershell
run: |
iisreset /restart
Write-Host "IIS restarted successfully!"
You can also add your database deployments here to ensure your deployment matches any database changes.
Important Notes
- Secrets: Make sure
DB_CONNECTION_STRINGis set in your repository's secrets (or the runner's environment) so it's available during the workflow run. - Paths: Adjust the paths (project folder names, output directories) if your solution structure differs.
- Permissions: Ensure the relevant folders (e.g.
C:\inetpub\YourProjectFolder,C:\dotnet) have the appropriate permissions for the IIS application pool or service account running your app.
Step 4: Running the Workflow and Debugging
Once I committed the file and pushed changes, GitHub Actions triggered the workflow. I ran into a few issues along the way:
- Permissions: My self-hosted runner needed admin privileges, so I ran
run.cmdin an elevated PowerShell window. - Port Binding Conflicts: IIS sometimes locked the port, so I used
net stop was /yto reset IIS. - Versions of dependencies: If any dependencies (.NET, Node, Umbraco, etc.) need to be downloaded, ensure the versions match the project.
Once fixed, the pipeline successfully deployed my app after every push to master!
Conclusion
Setting up CI/CD on a Windows 11 developer server using GitHub Actions was a great learning experience. It helped me automate deployments, understand self-hosted runners, and optimize IIS configurations.
If you're also hosting a project locally, this setup will save you time and ensure your application is always up to date with every push!