A full-stack task management application built incrementally over 12 weeks,
demonstrating real-world DevOps and Cloud Engineering workflows.
This project demonstrates how to implement DevOps and Cloud Engineering workflows starting from basic to advanced tools. The project spans 12 weeks, with each week introducing new tools and updates to improve the workflow and showcase real DevOps practices.
-
Install Node.js and restart your computer
-
Verify installation:
node -v && npm -v -
Create Frontend and Backend folders
-
Create
html,css, andjsfiles inside the frontend folder -
Create
server.jsandpackage.jsoninside the backend -
Install dependencies:
cd backend npm installThis creates:
node_modules/andpackage-lock.json -
Start the server:
node server.js
You should see:
Server running on http://localhost:3000 -
Test the API — open in your browser:
http://localhost:3000/tasksYou should see an empty array
[]
Outcome:
- ✅ Frontend working locally
- ✅ Backend working locally
- ✅ API endpoint returning empty array
-
Initialize git from the main project folder:
git init git status
-
Create a GitHub repo named
Task App(leave all defaults) and create -
Connect your local project to the repo:
git remote add origin https://github.com/iampryce/Task-App.git
-
Push local code to GitHub:
git branch -M main git add . git commit -m "Task App frontend + backend" git push -u origin main
Create a YAML workflow file that automatically runs when code is pushed to GitHub:
- Checks out the repo
- Sets up Node.js
- Installs backend dependencies
- Simulates a test
- Shows logs
- ✅ Pipeline green if everything works — ❌ fails if something breaks
-
Create the workflow folder:
mkdir -p .github/workflows
-
Create the CI file:
touch .github/workflows/ci.yml
The
ci.ymlfile tells GitHub what to do automatically when code is pushed. -
Write or copy the YAML config into
ci.yml -
Push to GitHub:
git add .github/workflows/ci.yml git commit -m "Week 2: Add CI pipeline" git push -
Watch it run:
- Go to your GitHub repo → Actions tab
- Click Week 2 CI Pipeline
- You should see: Checkout ✅ Setup Node.js ✅ Install dependencies ✅ Simulate test ✅
| GitHub Actions | Jenkins |
|---|---|
| Managed by GitHub servers | Self-managed server |
- Launch a Linux server
- Install Jenkins
- Access Jenkins via browser
- Connect Jenkins to GitHub repo
- Run first Jenkins build automatically
1. Launch a VM
Allow inbound traffic: SSH (22), HTTP (80), Custom TCP: 8080 (Jenkins)
2. Connect to Server and Install Java
sudo apt update
sudo apt install fontconfig openjdk-21-jre
java -version3. Install Node.js
sudo apt update
sudo apt install nodejs npm -y4. Install Jenkins
Use the official docs for Ubuntu/Debian: https://www.jenkins.io/download/
# Add Jenkins key
sudo wget -O /etc/apt/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2026.key
echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc]" \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
# Install
sudo apt update
sudo apt install jenkins
# Enable and start
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins5. Access Jenkins
Open in browser: http://your-IP:8080
Get the default password:
sudo cat /var/lib/jenkins/secrets/initialAdminPasswordComplete setup → Install suggested plugins → Create admin user → Save
6. Build Jenkins Pipeline Job
- Create new item → name it
task-app-jenkins-ci→ select Pipeline - Configure: Scroll to Pipeline → choose Pipeline script from SCM → SCM: Git → add repo URL → change branch to
main→ set script path toJenkinsfile→ Save - Create
Jenkinsfilewith pipeline script and push:git add Jenkinsfile git commit -m "Add Jenkins pipeline" git push - Go to Jenkins → click Build Now to verify
7. Enable GitHub Webhook Trigger
In Jenkins job → Configure → Build Triggers → ✅ GitHub hook trigger for GITScm polling → Save
In GitHub → Settings → Webhooks → Add Webhook:
- URL:
http://yourIP:8080/github-webhook/ - Content Type:
application/json - Events: Just the push event
Test it:
git add .
git commit -m "Test Jenkins webhook"
git pushJenkins will now build automatically on every push.
Docker packages your application and all its dependencies into a single standardized container that runs consistently across any environment — from a developer's laptop to the cloud.
1. Create the Dockerfile in the root of your project
2. Build Docker Image locally
docker build -t task-app .3. Run the Container
docker run -p 3000:3000 task-appOpen: http://localhost:3000
4. Push Dockerfile to GitHub
git add Dockerfile
git commit -m "Add Dockerfile for containerization"
git push5. Install Docker on Jenkins Server
sudo apt update
sudo apt install docker.io -y
sudo systemctl start docker
sudo systemctl enable docker6. Allow Jenkins to Use Docker
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins7. Update Jenkinsfile to add Docker build stage
Jenkins now: Pull repo → Install dependencies → Verify Node → Build Docker image
8. Verify the Docker build in Jenkins console output
9. Confirm the image exists on the server:
docker imagesPush Docker images to Docker Hub so they can be accessed from anywhere.
1. Create a repository on Docker Hub
- Log in at https://hub.docker.com
- Navigate to Repositories → Add name and description → Create
2. Login to Docker Hub on Jenkins Server
sudo docker loginUse the secure browser login: https://login.docker.com/activate
3. Tag your Docker image
sudo docker images
sudo docker tag [IMAGE_ID] [dockerusername]/[reponame]:task-appv14. Push the image
docker push [your-username]/[your-repo-name]:task-appv15. Verify on Docker Hub
Go to hub.docker.com → your repository → Tags tab
6. Pull and Run from anywhere
docker pull YOUR_DOCKER_USERNAME/your-repo-name:v1
docker run -p 8080:3000 YOUR_DOCKER_USERNAME/your-repo-name:v17. Automate in Jenkins
Update Jenkinsfile to add:
- Tag stage → prepares image for Docker Hub
- Push stage → uploads image automatically
# On Jenkins server
sudo su - jenkins
docker logingit add .
git commit -m "Automate Docker push in Jenkins"
git pushTerraform lets you define and create cloud infrastructure using code instead of manual clicks in the console.
- Install Terraform
- Connect Terraform to Azure
- Create a VM using code
- SSH into the server
1. Install Terraform and Azure CLI
- Download Terraform: https://developer.hashicorp.com/terraform/downloads
- Download Azure CLI: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli
Verify:
terraform -v
az versionLogin and configure Azure:
az login --use-device-code
az account show
az account list --output table
az account set --subscription "SUBSCRIPTION_ID"2. Create Terraform Files
mkdir terraform-ec2
cd terraform-ec2
touch main.tfCopy the Terraform configuration into main.tf.
3. Generate SSH Key Pair
ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/azure-devops-key.pem
ls ~/.ssh~/.ssh/azure-devops-key.pem→ private key (keep secure)~/.ssh/azure-devops-key.pem.pub→ public key (shared with server)
4. Run Terraform
terraform init # Downloads provider plugins
terraform plan # Preview changes
terraform apply # Create infrastructureSSH into your server:
ssh -i ~/.ssh/azure-devops-key.pem azureuser@YOUR_PUBLIC_IP5. Verify on Azure Portal
Open the portal and confirm all resources were created.
6. Clean Up
terraform destroyCreate a
.gitignorefile to exclude Terraform state files from being pushed to GitHub.
At this stage, we move from a single containerized application to a realistic production-ready structure with separate Frontend and Backend services.
| Service | Responsibility |
|---|---|
| Frontend | User interface (HTML, CSS, JavaScript) |
| Backend | App logic and APIs |
Kubernetes is a system that automatically runs and manages containerized applications. Instead of manually starting containers and restarting them when they fail, Kubernetes takes over these responsibilities and ensures the application is always running as expected.
Key benefits:
- Automatically restarts failed containers
- Scales up or down based on demand
- Manages communication between services using stable internal names (e.g.
backend-service) - Shifts container management responsibility from the developer to the system
-
Create a
Dockerfileinside bothfrontend/andbackend/folders (delete the old root Dockerfile) -
Build images:
docker build -t [username]/[backend-repo]:latest ./backend docker build -t [username]/[frontend-repo]:latest ./frontend
-
Verify images:
docker images
-
Push to Docker Hub:
docker push [backend-image] docker push [frontend-image]
1. Install Kubernetes (K3s) on VM
curl -sfL https://get.k3s.io | sh -
sudo kubectl get nodes2. Backend Deployment
mkdir k8sCreate k8s/backend-deployment.yaml with your backend image, then apply:
sudo kubectl apply -f backend-deployment.yaml
sudo kubectl get pods3. Create Backend Service
Create k8s/backend-service.yaml, push to GitHub, pull to VM, then apply:
sudo kubectl apply -f backend-service.yaml
sudo kubectl get svcYou should see a ClusterIP and port 3000 listed.
4. Frontend Deployment
Create k8s/frontend-deployment.yaml and k8s/frontend-service.yaml, push and pull to VM, then apply:
sudo kubectl apply -f frontend-deployment.yaml
sudo kubectl apply -f frontend-service.yaml
sudo kubectl get pods
sudo kubectl get svc5. Verify
curl http://localhost:30007If you get HTML back — Kubernetes is working and the frontend is running ✅
6. Open firewall port
Update main.tf NSG rules:
security_rule {
name = "allow-frontend"
priority = 1004
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "30007"
source_address_prefix = "*"
destination_address_prefix = "*"
}terraform applyTest in browser: http://YOUR-VM-IP:30007
Every push triggers a full automated pipeline:
Push Code → Jenkins builds images → Jenkins pushes to Docker Hub → Jenkins updates Kubernetes → Kubernetes pulls new image → Rolling update
1. Update your Jenkinsfile to build both services, push to Docker Hub, and deploy to Kubernetes
2. Generate a Docker Hub Access Token
- hub.docker.com → Profile → Account Settings → Security → New Access Token
- Description:
jenkins-ci| Access: Read & Write - Copy the token immediately — you won't see it again
3. Add credentials to Jenkins
Manage Jenkins → Credentials → System → Global → Add Credentials
| Field | Value |
|---|---|
| Kind | Username with password |
| Username | your Docker Hub username |
| Password | paste the access token |
| ID | dockerhub-creds |
| Description | Docker Hub Access Token |
4. Give Jenkins access to Kubernetes
SSH into your VM:
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
sudo usermod -aG sudo jenkins5. Add Jenkins to sudoers
sudo visudoAdd at the bottom:
jenkins ALL=(ALL) NOPASSWD: /usr/local/bin/kubectl
6. Restart Jenkins
sudo systemctl restart jenkins7. Test the full pipeline
git add .
git commit -m "add jenkins cicd"
git push8. Verify deployment
kubectl get podsYou should see newly created pods with a recent AGE — confirming Jenkins built, pushed, and Kubernetes deployed the update automatically ✅
9. End-to-End Final Test
- Make a small change in the HTML file
- Commit and push
- Verify pods:
kubectl get pods - Open the app in the browser and confirm changes are live
This phase introduces full observability into the system by deploying a monitoring stack inside Kubernetes. Prometheus collects metrics from the cluster, and Grafana provides a visual dashboard to explore them in real time.
1. Install Helm on your server
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm versionHelm installs complex applications into Kubernetes using pre-configured packages. Instead of writing many YAML files manually, Helm handles it for you.
2. Add Prometheus Repository
helm repo add prometheus-community https://prometheus-community.github.io/helm-chartsConnects Helm to the public repository that contains ready-made configurations for Prometheus and Grafana.
3. Update Helm Repositories
helm repo updateFetches the latest versions of all packages, ensuring you install the most up-to-date monitoring stack.
4. Configure Kubernetes Access
sudo chmod 644 /etc/rancher/k3s/k3s.yamlGives your user permission to read the Kubernetes config file. Without this, Helm and kubectl cannot connect to the cluster.
5. Export KUBECONFIG
export KUBECONFIG=/etc/rancher/k3s/k3s.yamlTells your system which config file to use to connect to Kubernetes.
Test the connection:
kubectl get nodes6. Install Monitoring Stack
helm install monitoring prometheus-community/kube-prometheus-stackDeploys a complete monitoring system into your Kubernetes cluster in one command — Prometheus, Grafana, and Alertmanager all included.
7. Verify Installation
kubectl get podsYou should see new pods for Prometheus, Grafana, and Alertmanager — confirming the monitoring stack is running ✅
8. Configure Grafana Service Ports
Check the current Grafana service:
kubectl get svc monitoring-grafanaYou will see TYPE: ClusterIP — meaning it's internal only and not accessible from outside the cluster.
Create k8s/grafana-service.yaml with the following:
apiVersion: v1
kind: Service
metadata:
name: monitoring-grafana
namespace: default
spec:
type: NodePort
selector:
app.kubernetes.io/instance: monitoring
app.kubernetes.io/name: grafana
ports:
- name: http-web
port: 80
protocol: TCP
targetPort: 3000
nodePort: 32000Push to GitHub:
git add .
git commit -m "Expose Grafana via NodePort"
git pushPull on your VM:
cd Task-App
git pullDelete the old service and apply the new one:
kubectl delete svc monitoring-grafana
kubectl apply -f k8s/grafana-service.yaml
kubectl get svc monitoring-grafanaYou should now see: NodePort 80:32000/TCP ✅
Update terraform-ec2/main.tf to open port 32000 and apply:
security_rule {
name = "allow-grafana-nodeport"
priority = 1009
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "32000"
source_address_prefix = "*"
destination_address_prefix = "*"
}terraform apply9. Login to Grafana
Open in your browser: http://YOUR_SERVER_IP:32000
Get the admin password:
kubectl get secret monitoring-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo| Field | Value |
|---|---|
| Username | admin |
| Password | your output |
10. Open Kubernetes Dashboards
In Grafana you should see pre-built folders:
- Kubernetes / Compute Resources / Node
- Kubernetes / Compute Resources / Pod
- Kubernetes / Networking
Launch your app, perform some tasks, then return to Grafana and watch the metrics spike in real time.
GitHub → Jenkins → Docker → Kubernetes → App runs → Prometheus collects data → Grafana shows it
This phase introduced the feedback layer into the DevOps workflow. Without monitoring, systems operate blindly — making it difficult to detect issues or understand performance. With Prometheus and Grafana in place, the cluster can now observe itself, tracking CPU usage, memory, pod health, and application resource usage in real time.
The pipeline has evolved beyond just building and deploying. It now includes full observability — a foundational requirement for any production-ready environment.
