Master Git and Github Workflow for Devops: Base of Everything


Hello, Amazing People! Welcome to the second blog in our DevOps for Developers series. Today, we’re diving deep into Git and GitHub, the backbone of version control and collaboration in DevOps. Whether you’re a beginner or leveling up your DevOps game, this post will equip you with the knowledge to master Git and GitHub like a pro. We’ll cover everything from commits to CI/CD integration, with practical tips to streamline your workflow. Let’s get started!
Git and Github Basics
What is Git? : Git is a distributed version control system that tracks changes your code, allowing multiple developers to collaborate seamlessly.
What is Github? : GitHub is a platform for hosting Git repositories, enabling collaboration, code reviews, and automation.
Commits: A commit is a snapshot of your changes, like saving a checkpoint in a game. Use
git commit -m "Your message"
to save your work.Branches: Branches let you work on features or fixes in isolation. Create one with
git branch feature-name
and switch usinggit checkout feature-name
.Git Workflow Setup: A typical workflow involves cloning a repo (
git clone
), making changes, committing, and pushing to GitHub (git push
).Resolving Merge Conflicts: When changes clash, Git flags conflicts. You resolve them by editing the conflicting files, then running
git add.
andgit commit
.
Pull Requests: Collaboration in Action
Alright, Amazing People, now that you’ve got the basics down, let’s talk about pull requests (PRs)—the secret sauce of team collaboration in DevOps. A PR is how you propose merging your branch’s changes into the main codebase, usually the main or develop branch. It’s like saying, “Hey team, check out my work and let’s make it official!”
Here’s how to create a PR:
Push your branch to GitHub:
git push origin feature-name
.Head to GitHub, click “New Pull Request,” and select your branch to compare with the target (e.g., main).
Write a clear description of your changes (e.g., “Added a shiny new login page!”).
Your team reviews the code, leaves feedback, and approves. Once it’s good to go, merge the PR and celebrate!
Why PRs Rock in DevOps:
They ensure code quality through peer reviews, catching bugs before they sneak into production.
They keep a record of changes and discussions, so you always know who did what and why.
They trigger automated tests and deployments (more on that in a bit!).
Link your PR to issues (e.g., “Closes #123”) to keep your project organized and your team in the loop.
Branching Strategies for DevOps
In DevOps, how you manage branches can make or break your workflow. A good branching strategy keeps your project organized and your team sane. Here are two popular approaches:
Git Flow: Think of this as a structured playbook. You have a main branch for production-ready code, a develop branch for integrating new features, and separate branches for features (e.g., feature/new-button) or bug fixes. It’s perfect for projects with planned releases, like a big app update.
Trunk-Based Development: This is the fast lane. Everyone works off the main branch, using short-lived feature branches that get merged quickly. It’s ideal for teams pushing updates daily with CI/CD.
If you’re on a small team or love rapid deployments, go trunk-based for simplicity. For larger projects with formal release cycles, Git Flow keeps things tidy.
Github Actions : Your First Step in CI/CD
Now for the exciting part—GitHub Actions! This is where Git meets DevOps automation. GitHub Actions automatically run workflows when specific events happen in your repository, like pushing code or creating a PR. It's like having a tireless assistant that tests, builds, and deploys your code 24/7.
For practical explanation I gonna make a basic node.js project and going to integrate CI/CD pipeline into so let’s get started
Our pipeline will be super simple:
Push code → GitHub Actions kicks in
Test & Build → Make sure everything works
Deploy → Put it live automatically using Render
Let's build this step by step using a simple Node.js web application as our example.
Create a basic node.js app with express installed in it.
{ "name": "demo-app", "version": "1.0.0", "scripts": { "start": "node server.js", "test": "node test.js" }, "dependencies": { "express": "^5.1.0" }
//server.js const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; app.get('/', (req, res) => { res.json({ message: 'Hello CI/CD World!', version: '1.0.0' }); }); app.get('/health', (req, res) => { res.json({ status: 'OK' }); }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); module.exports = app;
//test.js (In real world testing we could use jest or mocha) const http = require('http'); const app = require('./server.js'); // Start server for testing const server = app.listen(3001, () => { console.log('Test server started on port 3001'); }); // Simple test function function runTests() { console.log('🧪 Running tests...'); let testsCompleted = 0; const totalTests = 2; // Test 1: Health check const healthReq = http.get('http://localhost:3001/health', (res) => { if (res.statusCode === 200) { console.log('✅ Health check passed'); } else { console.log('❌ Health check failed'); cleanup(1); return; } testsCompleted++; if (testsCompleted === totalTests) { console.log('🎉 All tests passed!'); cleanup(0); } }); healthReq.on('error', (err) => { console.log('❌ Health check request failed:', err.message); cleanup(1); }); // Test 2: Main endpoint const mainReq = http.get('http://localhost:3001/', (res) => { if (res.statusCode === 200) { console.log('✅ Main endpoint test passed'); } else { console.log('❌ Main endpoint test failed'); cleanup(1); return; } testsCompleted++; if (testsCompleted === totalTests) { console.log('🎉 All tests passed!'); cleanup(0); } }); mainReq.on('error', (err) => { console.log('❌ Main endpoint request failed:', err.message); cleanup(1); }); } // Cleanup function to properly close server and exit function cleanup(exitCode) { server.close(() => { console.log('Test server closed'); process.exit(exitCode); }); // Force exit after 5 seconds if server doesn't close gracefully setTimeout(() => { console.log('Force closing...'); process.exit(exitCode); }, 5000); } // Handle process termination process.on('SIGINT', () => cleanup(1)); process.on('SIGTERM', () => cleanup(1)); // Add timeout for the entire test suite const testTimeout = setTimeout(() => { console.log('❌ Tests timed out'); cleanup(1); }, 30000); // 30 second timeout // Run tests after server starts setTimeout(() => { runTests(); }, 1000);
Create a simple CI/CD pipeline
Now create .github/workflows/cicd.yml
:
Ok so before moving further I just want to clear some jargons for you and here are they:
What is CI/CD ? : CI/CD stands for Continuous Integration and Continuous Deployment.
CI (Continuous Integration) means automatically testing and building your code every time you push changes to a shared repo (like GitHub).
CD (Continuous Deployment) means automatically deploying your code to a server (like production or staging) after it passes all tests.
Together, CI/CD helps developers ship code faster, safer, and more often.
name: Simple CI/CD Pipeline
# When to run this workflow
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# What to do
jobs:
# Job 1: Test our code
test:
name: Test Application
runs-on: ubuntu-latest
steps:
- name: Get our code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
# Job 2: Deploy (only when pushing to main)
deploy:
name: Deploy Application
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Get our code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm install
#find the deploy hook key from Render Dashboard of your application
- name: Trigger Render Deploy
run: |
curl -X POST ${{ secrets.RENDER_HOOK }}
Now let’s understand what that mean and how you can write your own YAML configuration as per your need.
🔖 Workflow Name
name: Simple CI/CD Pipeline
This is the name that will show up in GitHub's Actions tab. You can name it anything like Node.js CI/CD
, Auto Deploy
, etc.
🕒 Triggering Events
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
This tells GitHub:
Run this workflow every time someone:
pushes code to the
main
branch,creates or updates a pull request targeting
main
.
🔧 Jobs
Jobs are sets of steps that GitHub Actions runs — either in parallel or in sequence.
✅ Job 1: test
— Run Tests
jobs:
test:
name: Test Application
runs-on: ubuntu-latest
This job is named "Test Application".
It runs on a virtual machine with Ubuntu (Azure VM).
🪜 Steps in the test
job:
- name: Get our code
uses: actions/checkout@v4
- Clones your GitHub repo into the runner.
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- Installs Node.js version 18 on the machine.
- name: Install dependencies
run: npm install
- Installs your app dependencies from
package.json
.
- name: Run tests
run: npm test
- Runs your tests using
npm test
.
🚀 Job 2: deploy
— Deploy to Render
deploy:
name: Deploy Application
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
needs: test
: This means the deploy job will only run if thetest
job passes.if: github.ref == 'refs/heads/main'
: This condition ensures deployment only happens on themain
branch — not on PRs or other branches.
🪜 Steps in the deploy
job:
Same setup as before — checkout, install Node.js, install dependencies.
Then this:
- name: Trigger Render Deploy
run: |
curl -X POST ${{ secrets.RENDER_HOOK }}
This sends a POST request to Render using
curl
.The
srv-xxxxxx
part identifies your app on Render.The
DEPLOY_HOOK_KEY
is your secret key to allow the deploy (like a password).
💡 Tip: Instead of hardcoding the key, store it safely:
And then define RENDER_HOOK
in GitHub Settings → Secrets.
- Push the Code to Github and setup Render for First Time
We are using Render here because it’s simple and beginner-friendly. It takes care of heavy lifting like managing virtual machines, handling SSH access, and setting up infrastructure.
However, in real-world production environments, teams typically use more robust and customizable setups — involving tools like Docker, Kubernetes, AWS, and Terraform — for greater flexibility, control, and scalability in the CI/CD pipeline.
git init
git remote add origin <your-repo-url>
git add .
git commit -m "initial commit"
git push -u origin main
After this, go to the Render Dashboard, deploy your code using the free plan, and generate a Deploy Hook.
This hook provides a secret URL (called the HOOK_SECRET) which you can use in your CI/CD pipeline to automatically trigger deployments whenever you push changes to your main branch.
Copy the Deploy Hook and look the key and put it in the secret section in settings of github.
https://api.render.com/deploy/srv-d17ss583e5dus73doie1g?key=6sssssfCf2yokg (dummy)
And configure a new environment and add secret that could be used in your workflow and in our case is RENDER_HOOK. (Project→Setting→Secrets and Variables→
Now as we are all ready to test the thing so let’s try to commit something to our main branch here.
In real-world projects, there are usually multiple contributors. When someone opens a pull request (PR), basic CI checks—like linting, running tests, and build verification—are automatically triggered.
If everything passes, a maintainer or reviewer reviews the code. Once approved, they merge the PR into the main
branch.
This is when CD (Continuous Deployment) comes into play: the pipeline picks up the change and automatically deploys it to production or staging environments.
After the change is pushed to main or merge to main branch the CI/CD pipeline starts and test the changes and after testing it gets deployed to render.
Now also check the Render Dashboard and you will notice the change.
This is your first small step into the world of DevOps — where we automated the CI/CD process using GitHub Actions and deployed code effortlessly with Render. You’ve now seen how tests, linting, and secure deploy hooks fit together in a basic pipeline. While this was a simple setup for learning, it mirrors real-world practices at a smaller scale. As we continue this "DevOps for Developers" series, we’ll dive deeper into topics like Docker, Kubernetes, Infrastructure as Code with Terraform, monitoring tools like Prometheus and Grafana, and advanced CI/CD workflows with AWS and GitHub Actions.
As a small challenge, try integrating Slack notifications into your workflow — send alerts when tests pass or fail, or when deployments begin. Push your changes and raise a PR! It’s a great way to reinforce what you’ve learned. Until then, happy coding — and stay curious as you build towards automating real-world production systems! 🚀🛠️
Subscribe to my newsletter
Read articles from Vishal Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Vishal Sharma
Vishal Sharma
I am a developer from Surat who loves to write about DSA,Web Development, and Computer Science stuff.