DevOps CI/CD 📦 🚚


DevOps Continuous Integration and Delivery (CI/CD) are practices that automate the software development process, enabling faster and more reliable releases.
What is CI/CD?
CI: Continuous Integration
Focuses on integrating code changes frequently and automatically testing them.
CD: Continuous Delivery
Automates the delivery and deployment of these changes to various environments.
Let’s visualize it moving in the following order:
Okay, that’s better. Thanks Whiskas 🐱!
This process enables developers to identify and fix issues early on in the development cycle, leading to a faster and more reliable release process. This is mostly achieved by using an open-source automation server called Jenkins.
Why CI/CD?
Before I set this up, deploying my app was a manual process - building the project locally, publishing, and then uploading it to Azure. This was slow, prone to mistakes, and hard to track.
By automating everything with CI/CD, every code change triggers a build, runs tests, and if all goes well, deploys to production automatically. It ensures higher code quality and faster delivery.
Building CI/CD Pipeline with GitHub Actions and Azure 🤖
Step 1: Creating the GitHub Workflow 👷♂️
I created a GitHub Actions workflow file .github/workflows/ci-cd.yaml to define the pipeline.
It contains two main jobs:
Build job:
Checks out the repo
Sets up the .NET 8 SDK
Restores dependencies
Builds the project
Runs unit tests and collects code coverage
Publishes the project as a self-contained Linux app
Zips the published output and uploads it as an artifact
Deploy job:
Downloads the build artifact
Logs into Azure using a service principal
Deploys the zipped app package to Azure App Service
Here’s a snippet from my workflow showing the build and deploy steps:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- run: dotnet restore
- run: dotnet build --no-restore
- run: dotnet test "MyProject.Tests/MyProject.Tests.csproj" --collect "XPlat Code Coverage"
- run: dotnet publish "MyProject.Web/MyProject.Web.csproj" -c Release -o ./publish --self-contained -r linux-x64 /p:UseAppHost=true
- run: |
cd publish
zip -r ../app.zip .
- uses: actions/upload-artifact@v4
with:
name: dotnet-webapp
path: app.zip
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dotnet-webapp
- uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- uses: azure/webapps-deploy@v2
with:
app-name: 'app-myprojectapi'
package: app.zip
Step 2: Preparing Azure
To allow GitHub Actions to deploy my app, I needed to set up Azure properly:
• Created an Azure App Service to host the API.
• Created an Azure Service Principal — this is an identity with permission to deploy to the App Service.
• Assigned necessary roles to the service principal to allow deployments.
• Saved the service principal credentials (a JSON string) as a secret called AZURE_CREDENTIALS
in GitHub.
If you’re interested, I can write a detailed post on how to create the service principal and set permissions.
Step 3: Configuring GitHub Secrets 🕵️♀️ 🔑
To keep sensitive data secure, I never put credentials directly into my workflow files.
Instead, I added these secrets to my GitHub repo under Settings > Secrets and variables > Actions:
• AZURE_CREDENTIALS
— The Azure service principal JSON credentials.
• SLACK_WEBHOOK_URL
(optional) — A Slack webhook URL so my pipeline can notify me about deployment results.
This way, the workflow can securely log in and notify me without exposing credentials publicly.
Step 4: Deploying and Testing 🧪
After pushing my workflow and code, every push to the main branch triggered the pipeline:
• The build job compiled the code, ran tests, and created an artifact.
• The deploy job logged into Azure and updated the running app.
• Slack sent me a notification about the deployment success.
I also added a simple health check endpoint (/health) to verify the API was running correctly on Azure.
Visiting the Azure App Service URL showed my API was live and responding!
Step 5: Adding Slack Notification 🔔
After the Deploy step I have added the following workflow to notify when a pipeline is triggered and what is the status of it.
This is useful in real project scenarios to track for failed build or deploy realtime.
Here is the code snippet added to the .yaml
file:
- name: Slack Notification
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,ref,workflow,job,took
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
After a pipeline is triggered via a change in the main
branch I get notification in my slack channel:
Challenges & Learnings 🧗♀️
Setting up the Azure service principal and permissions was the trickiest part. The official docs helped a lot. Because it is paid, was more challenging, apparently.. (who knew that 😅)
Publishing the app as a self-contained Linux app was necessary because Azure Linux App Service expects a self-contained deployment.
Using GitHub Actions’ caching and artifacts speeds up build times and keeps things organized.
Integrating Slack notifications was a nice bonus for monitoring deployments without checking GitHub constantly.
Conclusion 🎉
With just a few steps, I automated my entire build-test-deploy process for my ASP.NET Core API using GitHub Actions and Azure App Service. This pipeline saves me time and gives me confidence that only tested code reaches production.
Anyway, If you want to set up something similar or have questions, feel free to reach out or comment below.
Thank you, folks, you can join my channel so we can learn and thrive together! 🤓
Resources
https://kodekloud.com/blog/ci-cd-pipeline-in-devops/
Andy Thielking, DevOps Continuous Integration and Delivery: Setting up a CI/CD Pipeline
Subscribe to my newsletter
Read articles from Saphie Hanadi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Saphie Hanadi
Saphie Hanadi
A Software Developer, who loves writing JavaScript and explore new technologies and trends.