How to setup CI/CD pipeline using self-hosted GitHub Actions

Dipendra Neupane
codenp
Published in
5 min readNov 23, 2022

Back in 2018, GitHub introduced a platform-native automation tool called GitHub actions.

Photo by Roman Synkevych 🇺🇦 on Unsplash

GitHub users can use Actions to build their continuous integration and continuous delivery pipelines. And that’s pretty much the first thing most people think about when they hear about this tool. You can utilize this precious feature of GitHub to automate day-to-day tasks.

In this article, we’ll be creating a bare minimum Asp.Net Core Web API including an NUnit test project and setting up CI/CD pipeline using self-hosted GitHub actions.

Here are a few keywords you must know before getting started with the GitHub actions pipeline.

GitHub actions workflow

GitHub actions workflow is the configurable automated process written on a .yml file to run one or more jobs.

It will be checked into the repository and will be triggered when a specified event happened on a repository. A repository can have multiple workflow files and each of which can perform a different set of tasks.

Workflow files are defined in the .github/workflows directory within a repository.

Runner

A runner is an application that runs the job defined on the GitHub actions workflow file. GitHub provides its GitHub-hosted runner or you can create your self-hosted runner.

You can add a self-hosted runner to a repository, an organization, or an enterprise. We will be adding a self-hosted runner to a repository for this demo.

GitHub provides a bunch of commands to install a runner on your machine. The runner should be running to pick the job when a new event happens on the repository.

You can run a runner as a windows service. Please make sure the command prompt is opened in administrator mode while installing it.

After installation, the self-hosted GitHub actions runner for my repository is running on my local window’s service.

This is how it looks like on GitHub settings.

Now our self-hosted GitHub runner is ready to run our GitHub actions workflow file. Creating a workflow file is easy with the action menu on the repository itself.

GitHub has a bunch of pre-defined workflow file templates that you can choose and edit according to your requirement.

I have searched for .net and edited that template for our use.

name: AspNetCoreAPI

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

jobs:
build:
runs-on: [self-hosted]

steps:
- uses: actions/checkout@v1

- name: Set up .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'

- name: Build with dotnet
run: dotnet build src\AspNetCoreAPI-GitHubAction.Api\AspNetCoreAPI-GitHubAction.Api.csproj --configuration Release

test:
needs: build
runs-on: [self-hosted]

steps:
- name: Test
run: dotnet test src\AspNetCoreAPI-GitHubAction.Test

deploy:
needs: test
runs-on: [self-hosted]

steps:
- name: Project publish
run: dotnet publish -c Release src\AspNetCoreAPI-GitHubAction.Api\AspNetCoreAPI-GitHubAction.Api.csproj -o ${{env.DOTNET_ROOT}}/AspNetCoreAPI

- name: Deploy to IIS
run: |
if ((Get-WebSiteState -Name AspNetCoreAPI).Value -eq "Started")
{
Stop-WebSite -Name AspNetCoreAPI
echo "Stopped Website AspNetCoreAPI"
}
if ((Get-WebAppPoolState -Name AspNetCoreAPI).Value -eq "Started")
{
Stop-WebAppPool -Name AspNetCoreAPI
echo "Stopped Application Pool AspNetCoreAPI"
}

Start-Sleep -s 15
Copy-Item ${{env.DOTNET_ROOT}}/AspNetCoreAPI/* C:\inetpub\wwwroot\AspNetCoreAPI -Recurse -Force

if ((Get-WebSiteState -Name AspNetCoreAPI).Value -eq "Stopped")
{
Start-WebSite -Name AspNetCoreAPI
echo "Started Website AspNetCoreAPI"
}
if ((Get-WebAppPoolState -Name AspNetCoreAPI).Value -eq "Stopped")
{
Start-WebAppPool -Name AspNetCoreAPI
echo "Started Application Pool AspNetCoreAPI"
}

if ($lastexitcode -lt 8) { $global:lastexitcode = 0 }

This is the YAML file with file extension .yml defined inside the .github/workflows directory as mentioned earlier.

We also name it a workflow file. Let’s break it down into smaller pieces to understand it more clearly.

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

It has pre-defined jobs that need to be executed when a specified event occurs. i.e, when a developer pushes any changes or makes a new pull request on the master branch, then the runner will execute this workflow file.

build:    
runs-on: [self-hosted]

steps:
- uses: actions/checkout@v1

- name: Set up .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'

- name: Build with dotnet
run: dotnet build src\AspNetCoreAPI-GitHubAction.Api\AspNetCoreAPI-GitHubAction.Api.csproj --configuration Release

The jobs are defined in three steps, build, test, and deploy. All of these steps will run one after another. This step will build the .csproj with Dotnet version 6 or above.

actions/checkout@v1 checks out the repository under GitHub workspace, so the workflow can access it.

actions/setup-dotnet@v1 will configure the .NET SDK on our runner so that we can use the .NET CLI command.

test:
needs: build
runs-on: [self-hosted]

steps:
- name: Test
run: dotnet test src\AspNetCoreAPI-GitHubAction.Test

The needs: build command on the test step wants the previous build step to complete successfully before the test step gets triggered. It also makes sense that each job will only run if the previous job runs successfully.

deploy:
needs: test
runs-on: [self-hosted]

steps:
- name: Project publish
run: dotnet publish -c Release src\AspNetCoreAPI-GitHubAction.Api\AspNetCoreAPI-GitHubAction.Api.csproj -o ${{env.DOTNET_ROOT}}/AspNetCoreAPI

- name: Deploy to IIS
run: |
if ((Get-WebSiteState -Name AspNetCoreAPI).Value -eq "Started")
{
Stop-WebSite -Name AspNetCoreAPI
echo "Stopped Website AspNetCoreAPI"
}
if ((Get-WebAppPoolState -Name AspNetCoreAPI).Value -eq "Started")
{
Stop-WebAppPool -Name AspNetCoreAPI
echo "Stopped Application Pool AspNetCoreAPI"
}

Start-Sleep -s 15
Copy-Item ${{env.DOTNET_ROOT}}/AspNetCoreAPI/* C:\inetpub\wwwroot\AspNetCoreAPI -Recurse -Force

if ((Get-WebSiteState -Name AspNetCoreAPI).Value -eq "Stopped")
{
Start-WebSite -Name AspNetCoreAPI
echo "Started Website AspNetCoreAPI"
}
if ((Get-WebAppPoolState -Name AspNetCoreAPI).Value -eq "Stopped")
{
Start-WebAppPool -Name AspNetCoreAPI
echo "Started Application Pool AspNetCoreAPI"
}

if ($lastexitcode -lt 8) { $global:lastexitcode = 0 }

I have already set up the project on IIS having the name AspNetCoreAPI and the same for the Application pool as well.

Before moving the published file to the wwwroot directory, the first step will publish the project on the directory inside AspNetCoreAPI at DOTNET_ROOT. Later on, the next step will copy the published files to the pre-build directory inside wwwroot.

Application and application pool are stopped before moving the file so that copied files move without an issue.

Now my deployment process is automated using the GitHub actions pipeline. Any new push on the master branch will automatically publish the changes.

These green ticks are always satisfying in software development and everything looks perfect.

Here are workflow commands for GitHub actions and this is the GitHub repository that I have used for this article.

--

--

Dipendra Neupane
codenp
Editor for

Full-Stack Software Dev | When Dipendra isn't busy crafting code or captivating content, you can find him enjoying a good cup of americano or hitting the Gym.