Jenkins CI/CD Pipeline for a Dockerized Node.js Application: Manual Trigger vs Automatic Trigger Using GitHub Webhooks This article provides a step-by-step guide on building a CI/CD pipeline for a Dockerized Node.js application using Jenkins. It explains the transition from a manual deployment process, where a Jenkins job must be triggered by clicking "Build Now," to a fully automated system using GitHub webhooks that automatically triggers builds and deployments upon code pushes. The guide also covers essential setup steps, including installing Docker, granting Jenkins Docker permissions, and configuring GitHub authentication with personal access tokens. 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