Build a Real-World DevOps Pipeline with GitHub Actions, Terraform, EKS, and Argo CD (Step-by-Step)

Hi! I'm a highly motivated Security and DevOps professional with 7+ years of combined experience. My expertise bridges penetration testing and DevOps engineering, allowing me to deliver a comprehensive security approach.
Introduction
DevOps isnβt just a buzzwordβitβs the backbone of fast, secure, and scalable software delivery. If youβve ever dreamed of building a real-world CI/CD pipeline that goes from source code to production using industry-standard tools, youβre in the right place.
In this guide, Iβll walk you through the exact blueprint used by top engineering teams to deploy production-ready microservices using:
GitHub Actions for CI
Docker for containerization
AWS EKS + Terraform for scalable Kubernetes infrastructure
Argo CD for GitOps-based continuous delivery
ALB + ACM for HTTPS and secure ingress routing
By the end, you wonβt just understand the theoryβyouβll have a full, running system, complete with pipelines, infrastructure as code, GitOps deployment, and HTTPS-secured access via a custom domain.
ποΈ GitHub Repository
You can explore the full source code and pipeline configurations in the public repository below:
π GitHub Repo
π https://github.com/neamulkabiremon/ultimate-devops-project-demo
Inside the repo:
ultimate-devops-project-demo/
βββ .github/
β βββ workflows/ # CI pipelines for each microservice (GitHub Actions)
β βββ ad.yaml
β βββ checkout.yaml
β βββ frontend.yaml
β βββ recommendation.yaml
β βββ product-catalog-ci.yaml
β
βββ infrastructure/
β βββ eks-cluster/ # Complete Terraform config for AWS EKS & Add-ons
β βββ *.tf # EKS, VPC, IAM, ALB, ArgoCD, Metrics Server, etc.
β βββ values/ # Helm values and overrides
β
βββ kubernetes/
β βββ frontendproxy/ # Ingress, TLS, Certificate, and Service config
β βββ (other services)/ # K8s manifests for deployments, services, etc.
β
βββ argocd-apps/ # GitOps apps for Argo CD
β βββ frontend-app.yaml # Syncs K8s manifests from GitHub to the cluster
β
βββ src/ # Source code for all microservices
β βββ frontend/
β βββ ad/
β βββ checkout/
β βββ recommendation/
β βββ product-catalog/
β
βββ docker-compose*.yml # Local development & testing (optional)
Each .yaml file under .github/workflows defines a dedicated CI pipeline for an individual microservice, tailored to handle its testing, build, and deployment lifecycle. Youβre welcome to fork the repository, customize the pipelines, and run them in your own DevOps lab to sharpen your automation skills and experiment freely.
Step-by-Step: Frontend Microservice CI Pipeline
Our CI pipeline for the frontend microservice is structured around four key stages, ensuring thorough validation and a smooth, automated delivery process.
β Step 1: Set Up CI Pipeline (GitHub Actions)
To kick off our DevOps pipeline, weβll configure a robust Continuous Integration (CI) workflow using GitHub Actions. This pipeline will:
β Run unit tests to catch bugs early
β Enforce clean code using ESLint
β Build and push Docker images to Docker Hub
β Automatically update Kubernetes manifests
Weβll use frontend.yaml under .github/workflows/ to define the CI process for the frontend microservice.
Frontend CI Pipeline Workflow
.github/workflows/frontend.yaml
πΉ 1.1 β Workflow Triggers
The CI pipeline is triggered automatically based on code changes, ensuring continuous feedback and automation:
name: frontend-services-ci
on:
pull_request:
branches: [main]
push:
paths:
- '.github/workflows/frontend.yaml'
- 'src/frontend/**'
pull_request_target:
branches: [main]
paths:
- '.github/workflows/frontend.yaml'
- 'src/frontend/**'
Explanation of Triggers:
pull_request β Runs when a PR is opened or updated to the main branch.
push β Executes on direct commits to the workflow file or frontend source.
pull_request_target β Ensures safe execution of workflows triggered from forks (e.g., external contributors).
π‘ You can safely customize or remove these triggers based on your teamβs workflow needs.
πΉ 1.2 β Unit Testing
We begin by validating the code using unit tests, catching regressions and bugs early in the development lifecycle.
unit-testing:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: |
cd src/frontend
npm install
- name: Run unit tests
run: |
cd src/frontend
npm test -- --coverage --watchAll=false
πΉ 1.3 β Code Quality Checks (ESLint)
Next, we ensure the code meets industry standards using ESLint, which helps maintain readability and best practices.
code-quality:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: |
cd src/frontend
npm install
- name: Run ESLint (code quality check)
run: |
cd src/frontend
npm run lint -- --max-warnings=0 --quiet
π¨ The --max-warnings=0 flag ensures that even minor code issues will break the build β keeping code quality strict and production-grade.
πΉ 1.4 β Docker Build & Push
After successful validation, we package the frontend app into a Docker image and publish it to Docker Hub.
build-and-push:
runs-on: ubuntu-latest
needs: [unit-testing, code-quality]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Docker
uses: docker/setup-buildx-action@v1
- name: Login to Docker
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v3
with:
context: .
file: ./src/frontend/Dockerfile
push: true
tags: neamulkabiremon/frontend:${{ github.sha }}
π³ Using github.sha as the image tag ensures each build is uniquely identifiable and traceable.
πΉ 1.5 β Update Kubernetes Deployment
Finally, we auto-update the Kubernetes deployment manifest with the new Docker image tag and commit the change back to the repo.
update-k8s-deployment:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Update Kubernetes Deployment
run: |
sed -i "s|image: .*|image: ${{ secrets.DOCKER_USERNAME }}/frontend:${{ github.sha }}|" kubernetes/frontend/deploy.yaml
cat kubernetes/frontend/deploy.yaml
- name: Commit and push updated Kubernetes manifest
run: |
git config --global user.email "neamulkabiremon@gmail.com"
git config --global user.name "neamulkabiremon"
git add kubernetes/frontend/deploy.yaml
git commit -m "[CI]: Update frontend deployment image tag"
git push
π This ensures your Kubernetes manifests stay in sync with the latest Docker images β a key principle of GitOps.
β With this setup, you now have a fully automated CI pipeline that builds, tests, and prepares your microservice for deployment β all triggered by a simple Git push.
π Step 2: Securely Configure GitHub Secrets
To keep sensitive credentials like Docker Hub credentials and GitHub tokens safe and out of source control, weβll use GitHub Actions Secrets. These secrets are securely encrypted and only accessible by your GitHub workflows at runtime.
Secrets to Configure:

π Best Practice: Never hardcode these values in your workflow files. Always use ${{ secrets.SECRET_NAME }} to reference them.
How to Add Secrets in GitHub
Go to your GitHub repository.
Navigate to: Settings β Secrets and variables β Actions
Click on New repository secret

Add each secret (DOCKER_USERNAME, DOCKER_PASSWORD, and optionally GITHUB_TOKEN)
Save each one securely
βοΈ Step 3: Provision EKS with Terraform
In this section, we'll provision our Kubernetes cluster on AWS using Terraform. I will be using AWS EKS for this project, but you can choose any other cloud provider for Kubernetes. Terraform will help us automate and manage the infrastructure required for our application seamlessly.
Directory Structure
Navigate to:
cd infrastructure/eks-cluster
πΉ 3.1 β Initialize Terraform
Initialize your Terraform project to download necessary provider plugins and modules:
terraform init

πΉ 3.2 β Terraform Plan
To ensure that Terraform will create the desired resources:
terraform plan
This command will outline the resources that Terraform intends to create.
πΉ 3.3 β Terraform Apply
To provision your EKS cluster, run the following command:
terraform apply -auto-approve
This command creates the defined AWS resources without an interactive approval type.

β³ Wait for EKS & Node Group

The full provisioning (especially EKS cluster + node group) might take 10β15 minutes
πΉ 3.4 β Configure kubectl
After provisioning, verify your Kubernetes cluster is successfully created by configuring kubectl:
aws eks update-kubeconfig --region <your-region> --name <your-cluster-name>

Then test your connection:
kubectl get nodes

If successful, you will see the nodes of your newly created Kubernetes cluster listed.
π’ Step 4: Deploy to Kubernetes
Now that our EKS cluster is up and running, letβs manually deploy the entire application to verify that everything is functioning as expected.
Run the following command to deploy all Kubernetes manifests:
kubectl apply -f kubernetes/

β³ This will take around 2β5 minutes to roll out all services, deployments, and configurations.
π Expose the Application via LoadBalancer
By default, all services are deployed as ClusterIP, which is not accessible from the internet. To make the app available publicly, we need to change the frontendproxy service type to LoadBalancer.
Run this command:
kubectl patch service opentelemetry-demo-frontendproxy \
-p '{"spec": {"type": "LoadBalancer"}}'
Now, retrieve the external DNS or IP address assigned by AWS:
kubectl get service opentelemetry-demo-frontendproxy

Wait 2β10 minutes for the DNS to propagate. Once the EXTERNAL-IP field is populated, copy the address and open it in your browser:
http://a887d5c6ec63b4672b91878a986d4184-1991743320.us-east-2.elb.amazonaws.com:8080/

π You should now see the application frontend live! This is a full-scale production-like demo where you can browse products, simulate orders, and see distributed microservices in action.
π― Step 5: Install and Expose Argo CD
Now that the application is manually deployed, the next step is to automate deployments using Argo CD, following the GitOps methodology.
This means any changes pushed to your /kubernetes manifests (such as a new image tag from CI) will be automatically pulled and deployed to your EKS cluster by Argo CD.
πΉ 5.1 β Install Argo CD
First, create a namespace for Argo CD Then, install Argo CD using the official manifest:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

β This will deploy all Argo CD components (API server, controller, repo server, and UI) inside the argocd namespace.
πΉ 5.2 β Get ArgoCD Password
By default, the admin password is stored in a Kubernetes secret. Run the following command:
kubectl get secret argocd-initial-admin-secret \
-n argocd \
-o jsonpath="{.data.password}" | base64 --decode && echo

Copy the password n81w4ABZjGAj7qv9; we will need it later to log in to the Argo CD console.
πΉ 5.3 β Expose ArgoCD UI
The Argo CD API server is deployed as a ClusterIP service by default. You can patch it to LoadBalancer like this:
kubectl patch svc argocd-server \
-n argocd \
-p '{"spec": {"type": "LoadBalancer"}}'

Now get the external IP:
kubectl get service argocd-server -n argocd

Once the EXTERNAL-IP is available (may take 2β5 mins), open it in your browser:
a48e45918ac1e4657b8cf7a77e353f9b-761261883.us-east-2.elb.amazonaws.com

π§βπ» Log in to Argo CD Use the following default credentials:
Username: admin
Password: (use the one you just decoded)
π Step 6: Configure Argo CD App (GitOps)
Now that our application is running on the EKS cluster, letβs take it one step further and automate the deployment process using Argo CD. This way, every time we update a deployment manifest in our GitHub repo, Argo CD will automatically sync and apply it to the cluster β following the GitOps methodology.
Weβll configure Argo CD to watch the main branch of our GitHub repo and continuously apply any changes found inside the kubernetes/ directory.
1οΈβ£ Create the Argo CD Application YAML
Create a folder named argocd-apps and inside the folder create frontend-app.yamland paste the following configuration:
argocd-apps/frontend-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frontend
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/neamulkabiremon/ultimate-devops-project-demo
targetRevision: HEAD
path: kubernetes/frontend
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
Hereβs what each field means:
repoURL: Your public GitHub repo URL.
path: The folder containing all your Kubernetes manifests.
syncPolicy: Enables auto-sync, pruning, and self-healing to ensure your cluster always matches Git state.
2οΈβ£ Apply the Argo CD App to the Cluster
Now apply this config to create the Argo CD app for the frondend services:
kubectl apply -f argocd-apps/frontend-app.yaml
This will register the ultimate-devops-app inside Argo CD.
3οΈβ£ Verify from ArgoCD Dashboard
Open your Argo CD UI:
https://<your-argocd-external-ip>
Login using the admin username and decoded password.

Youβll now see a new application named frontend in the dashboard.
It will automatically sync the Kubernetes frontend manifests from the main branch of your repo and apply them to the cluster.
Step 7: Test the Complete Pipeline
With our Continuous Integration and Continuous Deployment pipelines fully configured using GitHub Actions and Argo CD, itβs time to validate everything works end-to-end. Weβll do this by making a small change in the frontend microservice β triggering the GitHub Actions workflow to:
Run tests and lint checks
Build and push a new Docker image
Update the deployment manifest in the kubernetes/frontend folder
Let Argo CD detect the change and sync the new deployment to the cluster
β Step-by-Step: Make a Change in the Frontend App
1οΈβ£ Open the frontend app source code
Go to:
src/frontend/components/Banner/Banner.tsx (or any visible UI component)
Open the file Banner.tsx and locate this line:
<S.Title>The best telescopes to see the world closer</S.Title>
Replace it with something more meaningful for your DevOps project. Hereβs a suggestion:
<S.Title>The best tools to see DevOps in action, closer than ever</S.Title>

β After Editing:
Save the file.
Push the change to GitHub:
git add .
git commit -m "feat: updated frontend banner message to reflect DevOps context"
git push origin main

- Your GitHub Actions CI pipeline should now trigger, build the new Docker image, and ArgoCD will auto-deploy it.
β Verifying the Complete CI/CD Pipeline
Now that weβve made a small update to the frontend application (the banner message), letβs validate that our complete CI/CD pipeline works as expected.
As soon as we push the updated code to GitHub, the pipeline is triggered automatically. Hereβs what happens under the hood:

π¬ Unit Testing:
The pipeline first runs comprehensive unit tests to catch bugs early and ensure the updated code doesnβt break any functionality.

π§Ή Code Quality Checks:
Next, it performs linting and code quality analysis to enforce clean coding standards using tools like ESLint.

π³ Docker Image Build & Push:
Once tests and quality checks pass, the pipeline builds a new Docker image using the updated code and pushes it to the configured Docker registry (e.g., Docker Hub).

π Kubernetes Manifest Update:
The pipeline then automatically updates the Kubernetes deployment YAML inside the /kubernetes directory with the new image tag (using the current Git SHA). This ensures our manifests always reflect the latest version.

π ArgoCD GitOps Deployment:

ArgoCD detected the change in the /kubernetes/frontend deployment file, pulls the updated manifest, and instantly applies it to the EKS cluster β completing the Continuous Delivery cycle.
π Testing the Updated Frontend
To verify the update (like banner text or frontend behavior), we can port-forward the service to our local machine using this command:
kubectl port-forward service/opentelemetry-demo-frontendproxy 8080:8080
Now open your browser and visit:

You should see the updated version of your frontend, confirming that the CI/CD pipeline and ArgoCD synchronisation worked correctly.
π Step 8: Enable HTTPS with ACM + ALB Ingress
Now that our application is deployed and CI/CD is fully functional, itβs time to secure it for real-world usage.
In this step, weβll expose the frontend proxy service using AWS ALB Ingress Controller and issue a valid SSL/TLS certificate via AWS Certificate Manager (ACM) β so users can access the app securely via https://.
β Why Use AWS ALB Ingress + ACM?
ALB provides scalable, managed Layer 7 routing
ACM allows you to issue free, auto-renewing SSL certificates for public domains
Seamless integration with Kubernetes via AWS Load Balancer Controller
We have already installed the AWS LoadBalancer controller and necessary components during the cluster provisioning, and the IAM role is attached properly to the service account and has permissions. Now we have to
πΉ 8.1 β Request ACM Certificate (manual via AWS Console)
Go to AWS Certificate Manager
Request a public certificate for your domain (e.g., *.example.com)

Validate the domain (via DNS or email)

Copy the ACM ARN once validated
πΉ 8.2 β Create Ingress Resource
Create a file called kubernetes/frontendproxy/ingress.yaml with the following:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frontend-proxy
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80, "HTTPS": 443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:377027906194:certificate/647f2856-e897-4c78-a4c0-45834c3ad14b
alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
ingressClassName: alb
rules:
- host: devops.neamulkabiremon.com
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: opentelemetry-demo-frontendproxy
port:
number: 8080
tls:
- hosts:
- devops.neamulkabiremon.com
β Replace your-domain.com with your actual domain.
β arn:aws:acm:REGION:ACCOUNT_ID:certificate/XXX with your ACM SSL cert ARN (must be in same region as your EKS cluster).
Example:
arn:aws:acm:us-east-2:377027906194:certificate/2e919de7-caf1-427d-afea-9b5b2b458a92
Apply the Ingress Resource
Once the ingress.yaml is ready with your custom domain and ACM certificate ARN, deploy it to your EKS cluster using:
kubectl apply -f kubernetes/frontendproxy/ingress.yaml

This will create an ALB (Application Load Balancer) and configure it with HTTPS termination using your ACM certificate.
πΉ 8.3 β Add Route53 CNAME Record
Now that the ALB is provisioned, get its DNS name using:
kubectl get ingress frontend-proxy
You will see an output like:

Copy the ADDRESS k8s-default-frontend-67db10afb2-681454757.us-east-1.elb.amazonaws.com
Then go to your DNS provider (e.g., Route 53 or your domain registrar) and add a CNAME record:

Type: CNAME
Value: a1b2c3d4e5f6g7h8.elb.amazonaws.com (use the actual value you got from the command above)
β οΈ DNS propagation may take a few minutes.
β Final Step: Test Secure Access
Once DNS is propagated, open your browser and visit:
https://devops.neamulkabiremon.com

You should now see your frontend application served securely over HTTPS with a valid SSL certificate.

Look for the π lock in the address bar β this confirms the TLS setup is working perfectly.
β What Weβve Achieved
By completing this step, you have:
Enabled secure HTTPS access to your application via a custom domain
Leveraged AWS ALB Ingress Controller for scalable ingress routing
Integrated ACM for free, managed, and auto-renewing SSL certificates
Set up production-grade traffic routing to your EKS workloads
Conclusion
Through this end-to-end DevOps pipeline implementation, weβve successfully built a production-ready microservices architecture that integrates:
π Continuous Integration with GitHub Actions
βΈοΈ Kubernetes infrastructure provisioned with Terraform on AWS EKS
π Continuous Deployment using Argo CD and GitOps
π HTTPS termination with AWS ACM + ALB Ingress Controller
π οΈ Modular and scalable architecture with real-world microservices
This project serves as a powerful foundation for deploying and managing cloud-native applications with reliability, speed, and security. Whether youβre preparing for production workloads, DevOps interviews, or client showcases, this setup reflects best practices in cloud-native DevOps engineering.



