Have you ever pushed code to GitHub and wished your application could automatically build and deploy itself without logging into a server or clicking a button in Jenkins? In this article, you'll learn how to build a complete CI/CD pipeline for a Dockerized Node.js application using Jenkins, starting with manual deployments and progressing to fully automated deployments using GitHub webhooks.
We will cover:
- Creating a Jenkins pipeline
- Building a Docker image
- Deploying a container
- Triggering builds manually
- Triggering builds automatically
- GitHub Personal Access Tokens
- Fine-grained vs Classic Tokens
- Jenkins credentials
- GitHub webhooks
- Required Jenkins plugins
- Common errors and troubleshooting The goal is to understand not only how to configure everything but also why each component is needed.
Node.js Application Repo URL- https://github.com/omkarsharma2821/Node.js-App-Deploy-Github-Action
Architecture Overview
The complete flow looks like this:
Developer
|
| Git Push
v
GitHub Repository
|
| Webhook
v
Jenkins
|
| Build Docker Image
v
Docker
|
| Run Container
v
Application Running
Without webhooks:
Developer
|
| Git Push
v
GitHub Repository
Jenkins Build Now (Manual Trigger)
|
v
Build and Deploy
With webhooks:
Developer
|
| Git Push
v
GitHub Repository
|
v
Webhook
|
v
Jenkins
|
v
Build and Deploy Automatically
Prerequisites
Before starting, ensure you have:
- Ubuntu Server
- Jenkins installed
- Docker installed
- Git installed
- GitHub repository
- Node.js application with Dockerfile Verify installations:
jenkins --version
docker --version
git --version
Installing Docker on Jenkins Server
Install Docker:
sudo apt update
sudo apt install docker.io -y
Enable Docker:
sudo systemctl enable docker
sudo systemctl start docker
Verify:
docker --version
Allow Jenkins to Use Docker
By default Jenkins cannot execute Docker commands.
Add Jenkins user to Docker group:
sudo usermod -aG docker jenkins
Restart Jenkins:
sudo systemctl restart jenkins
Verify:
sudo su - jenkins
docker ps
If Docker works without sudo, Jenkins is ready.
Creating the Pipeline
Initially we created a Jenkins pipeline that manually clones the repository.
Example:
pipeline {
agent any
stages {
stage('Clone Repository') {
steps {
sh '''
mkdir -p devops
cd devops
rm -rf Node.js-App-Deploy-Github-Action
git clone -b main https://github.com/username/repository.git
'''
}
}
stage('Build Image') {
steps {
sh '''
cd devops/Node.js-App-Deploy-Github-Action
docker build -t node-app .
'''
}
}
stage('Deploy') {
steps {
sh '''
docker run -d -p 8000:8080 node-app
'''
}
}
}
}
This works, but every deployment requires manually clicking:
Build Now
Problem with Multiple Deployments
Suppose the application is already running.
Running:
docker run -d -p 8000:8080 node-app
again will fail because port 8000 is already occupied.
Error:
Bind for 0.0.0.0:8000 failed
Better Deployment Approach
Before starting a new container, remove the old one.
docker rm -f node-app-container || true
Then start a new container:
docker run -d --name node-app-container -p 8000:8080 node-app
Understanding docker rm -f node-app-container || true
Let's break it down.
docker rm
Removes a container.
docker rm node-app-container
Works only if container is stopped.
-f
Force remove.
docker rm -f node-app-container
This:
- Stops container
- Removes container
||
OR operator.
Syntax:
command1 || command2
If command1 fails, command2 executes.
true
Always returns success.
true
Exit code:
0
Final Meaning
docker rm -f node-app-container || true
If container exists:
Remove it
If container doesn't exist:
Ignore error and continue
This prevents Jenkins from failing.
Manual Triggering
The simplest approach is manual execution.
Navigate to:
Jenkins Job
|
└── Build Now
Advantages:
- Easy to understand
Good for learning
Disadvantages:Requires human intervention
Not real CI/CD
Automatic Triggering
The goal of CI/CD is:
Code Push
|
v
Automatic Build
|
v
Automatic Deployment
This is where GitHub webhooks come into play.
Required Jenkins Plugins
Install the following Jenkins plugins before configuring the CI/CD pipeline:
1. Git Plugin
- Enables Jenkins to interact with Git repositories.
- Allows Jenkins to clone repositories, fetch changes, and checkout specific branches.
- Required for integrating Jenkins with GitHub repositories.
2. GitHub Plugin
- Provides integration between Jenkins and GitHub.
- Allows Jenkins to communicate with GitHub repositories and services.
- Supports GitHub-related features within Jenkins.
3. GitHub Integration Plugin
- Enables GitHub webhook support.
- Allows Jenkins to automatically trigger builds when code is pushed to GitHub.
- Essential for implementing automated CI/CD workflows.
4. Pipeline Plugin
- Enables support for Jenkins Pipelines.
- Allows execution of Jenkinsfiles written in Declarative or Scripted Pipeline syntax.
- Required for defining CI/CD workflows as code.
5. Credentials Plugin
- Provides secure storage for sensitive information.
- Allows storing:
- GitHub Personal Access Tokens (PATs)
- Usernames and passwords
- SSH keys
- API tokens
- Prevents hardcoding secrets in Jenkins jobs and pipelines.
GitHub Authentication
When Jenkins needs to access a GitHub repository, authentication requirements depend on the repository type.
Public Repository
- Can typically be cloned without authentication.
Example:
git clone https://github.com/username/repository.git
Private Repository
- Requires authentication.
- GitHub no longer supports account passwords for Git operations.
- A Personal Access Token (PAT) must be used instead of a password.
Why Use a Personal Access Token (PAT)?
- More secure than passwords.
- Allows granular permission control.
- Can be revoked without affecting your GitHub account password.
- Recommended by GitHub for all Git operations requiring authentication.
Classic Personal Access Token
Older token type.
Advantages:
- Simple
Easy to configure
Disadvantages:Broad permissions
Less secure
Example scopes:
repo
workflow
admin:repo_hook
Fine-Grained Personal Access Token
Newer and recommended approach.
Advantages:
- Repository-level access
- Better security
- Granular permissions Example:
Repository Access:
Only selected repositories
Permissions:
Contents: Read and Write
Metadata: Read
Webhooks: Read and Write
Fine-Grained vs Classic Token
| Feature | Fine-Grained | Classic |
|---|---|---|
| Security | High | Lower |
| Repository Scope | Specific | Broad |
| Permission Control | Granular | Broad |
| Recommended | Yes | Legacy |
For modern projects, prefer Fine-Grained tokens.
Adding GitHub Token to Jenkins
Navigate to:
Manage Jenkins
|
Credentials
Select:
Global Credentials
Choose:
Add Credentials
Kind:
Username with Password
Example:
Username: GitHub Username
Password: Personal Access Token
ID:
github-creds
Save.
Pipeline Script vs Pipeline Script from SCM
Many beginners get confused here.
Pipeline Script
Pipeline stored inside Jenkins UI.
Example:
pipeline {
agent any
}
Advantages:
Quick setup
Disadvantages:Not version controlled
Difficult to maintain
Pipeline Script from SCM
Pipeline stored in GitHub repository.
Repository structure:
project/
|
|-- Dockerfile
|-- package.json
|-- app.js
|-- Jenkinsfile
Jenkins automatically downloads Jenkinsfile.
Advantages:
- Version controlled
- Industry standard
- Easier maintenance Recommended approach.
Configuring Pipeline from SCM
Create Jenkins job.
Select:
Pipeline
Under Definition:
Pipeline script from SCM
SCM:
Git
Repository URL:
https://github.com/username/repository.git
Branch:
*/main
Script Path:
Jenkinsfile
Save.
Creating GitHub Webhook
Navigate to:
GitHub Repository
|
Settings
|
Webhooks
|
Add Webhook
Payload URL:
http://JENKINS_PUBLIC_IP:8080/github-webhook/
Content Type:
application/json
Event:
Just the push event
Save webhook.
Configuring Jenkins Trigger
Open job configuration.
Under Build Triggers:
Select:
GitHub hook trigger for GITScm polling
Save.
Testing the Webhook
Push code:
git add .
git commit -m "testing webhook"
git push origin main
Expected flow:
GitHub Push
|
v
Webhook
|
v
Jenkins
|
v
Pipeline Starts
No manual click required.
Common Troubleshooting
Webhook Returns 404
Cause:
Wrong webhook URL
Correct:
http://SERVER-IP:8080/github-webhook/
Webhook Returns 403
Cause:
Authentication or security issue
Verify:
- GitHub plugin
- GitHub integration plugin
Webhook Returns 200 But Build Doesn't Start
Common cause:
Pipeline Script instead of Pipeline Script from SCM
or
Repository mapping issue
Dockerfile Not Found
Example:
unable to evaluate symlinks in Dockerfile path
Cause:
Wrong working directory.
Check:
pwd
ls -la
Verify Dockerfile location.
Permission Denied While Running Docker
Cause:
Jenkins not in docker group
Fix:
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins
Final Jenkinsfile
pipeline {
agent any
stages {
stage('Build Image') {
steps {
sh '''
docker build -t node-app .
'''
}
}
stage('Deploy') {
steps {
sh '''
docker rm -f node-app-container || true
docker run -d --name node-app-container -p 8000:8080 node-app
'''
}
}
}
}
Conclusion
A Jenkins pipeline can be triggered manually or automatically. Manual triggering is useful for learning and testing, but real CI/CD begins when code pushes automatically trigger builds and deployments.
The recommended production approach is:
- Store the Jenkinsfile in GitHub.
- Use Pipeline Script from SCM.
- Configure GitHub credentials using a Personal Access Token.
- Enable GitHub webhook integration.
- Use Docker for packaging and deployment.
- Remove old containers before deploying new versions. With this setup, every code push automatically builds a Docker image, deploys a fresh container, and updates the application without requiring any manual intervention.
✍️ Author: Omkar Sharma
📬 Feel free to connect on LinkedIn or explore more on GitHub
Top comments (0)