Skip to main content

Command Palette

Search for a command to run...

Hands-on Jenkins CI Project (Intermediate) – Failure-Driven Debugging on AWS

Building a Jenkins CI pipeline on AWS EC2 by intentionally breaking things and fixing them like in real production environments.

Published
12 min read

1️⃣ Introduction

In this article, I document a hands-on Jenkins CI project built from scratch on a fresh AWS EC2 instance.
Instead of focusing only on a successful pipeline, I intentionally introduced real-world failure scenarios such as network misconfiguration, service access issues, webhook errors, and permission problems.

The objective of this project is to understand:

  • How Jenkins behaves under failure

  • How to debug CI issues systematically

  • How real DevOps engineers troubleshoot production-like problems

This project is written in a failure-driven learning approach, which closely reflects real industry environments.


2️⃣ Project Level & Scope

Project Level: Intermediate
Category: DevOps / CI / Jenkins

This project goes beyond beginner tutorials by focusing on:

  • Infrastructure-level issues (ports & security groups)

  • Jenkins service setup and debugging

  • GitHub webhook failures

  • Docker permission and integration issues

  • Root cause analysis and fixes


3️⃣ Tools & Technologies Used

  • AWS EC2 – Infrastructure

  • Ubuntu 22.04 LTS – Operating System

  • Jenkins – CI automation

  • GitHub – Source code & webhook trigger

  • Docker – Build and containerization

  • Linux – System administration


4️⃣ Project Architecture (High-Level)

The CI flow in this project looks like this:

  1. Developer pushes code to GitHub

  2. GitHub webhook triggers Jenkins automatically

  3. Jenkins pulls the code

  4. Jenkins builds the application using Docker

  5. Build results are visible in the Jenkins dashboard

This simple architecture is intentionally kept minimal to focus on debugging and learning, not complexity


5️⃣ Environment Setup – AWS EC2 Instance

I started by launching a new EC2 instance to ensure a clean environment.

EC2 Configuration:

  • AMI: Ubuntu 22.04 LTS

  • Instance Type: t3.micro

  • SSH Access: Enabled (port 22)

⚠️ Important (Intentional Failure):
At this stage, port 8080 was NOT opened in the security group.
This was done intentionally to simulate a common real-world mistake.


6️⃣ Phase 1 – Jenkins Installation

After connecting to the EC2 instance via SSH, I first updated the system packages and installed Java, which is a mandatory prerequisite for running Jenkins.

☕ Installing Java (OpenJDK 17)

sudo apt update
sudo apt install openjdk-17-jdk -y

Once installed, I verified the Java installation by checking the version:

java -version

Expected output:

openjdk version "17.x.x"

This confirmed that Java was installed successfully and ready for Jenkins.


🧩 Installing Jenkins

Next, I installed Jenkins using the official Jenkins repository to ensure a stable and supported installation.

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null

After adding the repository, I installed Jenkins and started the service:

sudo apt update
sudo apt install jenkins -y
sudo systemctl start jenkins
sudo systemctl enable jenkins

🔍 Verifying Jenkins Service Status

To confirm that Jenkins was running correctly at the service level, I checked its status:

sudo systemctl status jenkins

At this point, Jenkins was successfully installed and running on the EC2 instance.


⚠️ Important Note (Intentional Failure Setup)

Although the Jenkins service was running, the Jenkins web interface was not yet accessible because port 8080 was not allowed in the EC2 security group.
This was an intentional configuration choice to simulate a real-world failure scenario, which will be addressed in the next phase.


7️⃣ Failure Scenario 1 – Jenkins UI Not Accessible

After installing Jenkins and confirming that the service was running, I attempted to access the Jenkins web interface using the browser:

http://<EC2-PUBLIC-IP>:8080

❌ However, the Jenkins UI did not load and resulted in a connection timeout.


🔍 Root Cause Analysis

At this point, Jenkins was:

  • Installed correctly

  • Running as a service

  • Listening on port 8080

The issue was not with Jenkins itself.

On closer inspection, I found that:

  • Port 8080 was not allowed in the EC2 security group

  • AWS blocks inbound traffic by default unless explicitly permitted

This confirmed that the problem was at the network (infrastructure) level, not the application level.


🔧 Fix – Allowing Port 8080 in Security Group

To resolve this issue, I updated the EC2 security group inbound rules to allow Jenkins traffic.

I added a new inbound rule with the following configuration:

  • Type: Custom TCP

  • Port Range: 8080

  • Source: My IP / Anywhere (for learning purposes)

Click on Save rules.


✅ Verification

After updating the security group, I refreshed the browser and accessed Jenkins again:

http://<EC2-PUBLIC-IP>:8080

This time, the Jenkins Unlock Jenkins screen loaded successfully.


🧠 Key Learning

This issue highlights a common real-world scenario where:

  • Services are running correctly

  • But remain inaccessible due to missing network permissions

Understanding where to debug (application vs infrastructure) is a critical DevOps skill.

You can paste this directly after Phase 1 failure section.


8️⃣ Phase 2 – Jenkins Initial Setup & Unlocking Jenkins

Once the Jenkins web interface became accessible, the next step was to complete the initial Jenkins setup.


🔐 Retrieving the Initial Admin Password

When Jenkins is accessed for the first time, it requires an initial admin password, which is stored on the server.

I retrieved the password using the following command:

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

This command printed a one-time password used to unlock Jenkins.


🔓 Unlocking Jenkins

I pasted the retrieved password into the Unlock Jenkins screen in the browser and proceeded to the next step.


Jenkins then prompted for plugin installation.
I selected:

👉 Install suggested plugins

This installs commonly used plugins required for:

  • Git integration

  • Build execution

  • Jenkins UI stability

The installation took a few minutes to complete.


👤 Creating the First Admin User

After plugin installation, Jenkins prompted for admin user creation.
I created a dedicated admin user instead of continuing with the default setup.

This is a recommended best practice for:

  • Better access control

  • Auditability

  • Production-like setup


🎉 Jenkins Dashboard Access

Once the setup is completed, click on “Start using Jenkins”.

At this stage:

  • Jenkins was fully installed

  • Plugins were configured

  • Admin user was created

  • The Jenkins environment was ready for CI/CD configuration


9️⃣ Phase 3 – CI Pipeline Validation Using Jenkins Pipeline

With Jenkins fully set up, the next step was to validate a CI pipeline by creating a Pipeline job and running it manually before introducing any automation.

This phase focuses on establishing a baseline for the pipeline and understanding how Jenkins executes pipeline code from a repository.


🎯 Objective

  • Create a Jenkins Pipeline job

  • Load the pipeline from a GitHub repository

  • Execute the pipeline manually

  • Observe and validate pipeline behavior


🔧 9.1 Creating a Jenkins Pipeline Job

From the Jenkins dashboard:

  1. Click New Item

  2. Enter a job name:

     Jenkins-CI-CD-Demo
    
  3. Select Pipeline

  4. Click OK


🔗 9.2 Configuring Pipeline from SCM

In the job configuration, the pipeline was configured to load from GitHub:

  • Definition: Pipeline script from SCM

  • SCM: Git

  • Repository URL:

      https://github.com/Kunja-Ravikiran/Jenkins-CI-CD-Demo
    
  • Branch: main

  • Script Path:

      Jenkinsfile
    

This configuration ensures that Jenkins reads the pipeline definition directly from the repository, enabling version-controlled CI pipelines.


▶️ 9.3 Manual Pipeline Execution

After saving the configuration, the pipeline was triggered manually using:

👉 Build Now

This manual execution step is important to:

  • Validate repository connectivity

  • Verify Jenkinsfile detection

  • Establish a working baseline before automation


🔍 9.4 Observing Pipeline Output

The pipeline execution was monitored via the Console Output, which displays:

  • Stage-wise execution

  • Command outputs

  • Any errors encountered during the run

At this stage, the pipeline either:

  • Completed successfully, or

  • Failed with a clear error message (to be analyzed in subsequent steps)


🔴 9.5 Debugging the Initial Pipeline Failure

After completing the manual pipeline execution in Step 9.4, the build failed, which was clearly indicated by the red status in the Jenkins dashboard.


🔍 Locating the Error

To identify the cause of the failure, I opened the failed build and navigated to the Console Output section.
The console output provides a step-by-step execution log of each pipeline stage and is the primary source for debugging Jenkins failures.


🧠 Understanding the Failure

From the console logs, the following observations were made:

  • Jenkins successfully checked out the source code from GitHub

  • The Jenkinsfile was detected and executed correctly

  • The pipeline entered the Docker build stage

  • The build failed with the message indicating that the Docker command was not found

This confirmed that:

  • Jenkins installation was correct

  • Pipeline configuration was valid

  • The failure occurred due to a missing dependency on the Jenkins host


🔍 Root Cause Analysis

The pipeline failed because:

  • The Jenkinsfile included a stage that builds a Docker image

  • Jenkins attempted to execute Docker commands on the EC2 instance

  • Docker was not installed on the Jenkins execution environment

Since Jenkins executes pipeline steps on the underlying operating system, any external tools referenced in the pipeline must already be available on that system.

This type of failure is common in CI/CD pipelines and highlights the importance of preparing the execution environment before running builds.


✅ 9.6 Fix – Installing Docker on the Jenkins Host

Once the root cause was identified, the next step was to prepare the Jenkins host to support Docker-based builds.


🔧 Installing Docker on EC2

I connected to the EC2 instance where Jenkins was running and installed Docker using the system package manager.

sudo apt update
sudo apt install docker.io -y

▶️ Starting and Enabling Docker Service

After installation, Docker was started and enabled to run automatically on system boot.

sudo systemctl start docker
sudo systemctl enable docker

🔎 Verifying Docker Installation

To confirm that Docker was installed successfully, I verified the Docker version.

docker --version

This confirmed that Docker was now available on the Jenkins host.


🔴 Phase 4 – Docker Permission Denied Error in Jenkins Pipeline

After installing Docker on the Jenkins host and re-running the pipeline, the build failed again, even though Docker was now available on the system.


🔍 Observing the New Failure

As with the previous failure, I navigated to the failed build and reviewed the Console Output to understand the cause.

This time, the error message clearly indicated a permission-related issue while Jenkins was trying to interact with the Docker daemon.


🔍 Root Cause Analysis

The failure occurred because:

  • Jenkins runs as a dedicated jenkins user

  • Docker commands require access to the Docker daemon

  • By default, only users with appropriate permissions can access Docker

  • The jenkins user was not authorized to communicate with Docker

As a result, even though Docker was installed, Jenkins did not have the required permissions to execute Docker commands.

This is a very common real-world CI/CD issue when integrating Jenkins with Docker.


✅ Fix – Granting Docker Access to Jenkins User

To resolve this issue, the Jenkins user was granted permission to access Docker.

🔧 Updating User Permissions

I added the jenkins user to the Docker group on the EC2 instance.

sudo usermod -aG docker jenkins

This allows Jenkins to execute Docker commands without requiring root access.


🔄 Restarting Jenkins

After updating user permissions, Jenkins was restarted to ensure the new group membership took effect.

sudo systemctl restart jenkins

🔎 Verifying the Fix

Once Jenkins was restarted, I re-ran the pipeline from the Jenkins dashboard.

This time:

  • Jenkins successfully executed Docker commands

  • The Docker image was built without permission issues

  • The pipeline completed all stages successfully


🚀 Phase 5 – Automating the Pipeline using GitHub Webhooks

With the Jenkins pipeline now executing successfully through manual triggers, the next step was to automate the build process so that the pipeline runs automatically whenever code is pushed to the repository.

This eliminates the need for manual intervention and completes the CI workflow.


🔗 Configuring Jenkins for Webhook Triggers

In the Jenkins pipeline configuration, webhook-based triggering was enabled by selecting the option to trigger builds when GitHub sends push events.
This allows Jenkins to automatically start the pipeline whenever new code is pushed to the repository.

The pipeline was configured to load the Jenkinsfile directly from the GitHub repository, ensuring that both pipeline logic and source code remain version-controlled.


🔔 Adding a Webhook in GitHub

Next, a webhook was configured in the GitHub repository settings.

The webhook was set up to:

  • Notify Jenkins on every push event

  • Send payloads in JSON format

  • Target the Jenkins webhook endpoint.

GitHub webhook configuration


🔁 Verifying Automated Builds via GitHub Webhooks

After configuring the webhook in GitHub, the integration was verified by pushing a new commit to the repository.

Upon pushing the change:

  • GitHub sent a webhook event to Jenkins

  • Jenkins automatically triggered the pipeline

  • The build executed without any manual intervention

This confirmed that the CI pipeline was now fully automated and correctly integrated with GitHub.

As an optional verification step, the Docker container built by the Jenkins pipeline was run on the host and accessed via the browser. The application was successfully served through Nginx, confirming end-to-end CI automation.


🧾 Conclusion

In this project, I built a Jenkins-based CI pipeline from scratch and progressively enhanced it to support fully automated builds triggered by GitHub webhooks.

Instead of focusing only on the happy path, the pipeline was intentionally validated through real-world failure scenarios such as missing environment dependencies and permission issues. Each failure was diagnosed using Jenkins console logs and resolved at the appropriate layer, reinforcing the importance of structured debugging in CI/CD workflows.

By the end of the project, the pipeline was able to:

  • Execute as code using a Jenkinsfile

  • Build Docker images reliably

  • Handle environment and permission-related failures

  • Trigger automatically on every code push using GitHub webhooks

This hands-on approach closely reflects how CI pipelines behave in production environments and highlights the role of DevOps engineers in ensuring not just correct pipeline logic, but also a properly prepared execution environment.


🧠 Key Takeaways

  • Jenkins pipelines should always be validated manually before enabling automation

  • Console Output is the primary tool for debugging CI failures

  • CI pipelines depend on the execution environment as much as the pipeline code

  • Missing tools and permission issues are common and expected in real-world setups

  • GitHub webhooks enable reliable, event-driven CI automation without manual triggers


🔗 Final Notes

This project was designed as an intermediate-level CI implementation, focusing on practical debugging, environment readiness, and automation rather than complex application logic.

The complete source code and configuration used in this project are available in the associated GitHub repository.

This project focuses on building a robust Continuous Integration (CI) pipeline using Jenkins, Docker, and GitHub webhooks. The primary goal was to automate build validation and work through real-world CI failure scenarios such as missing dependencies, permission issues, and webhook automation.

Continuous Deployment (CD) was intentionally kept out of scope to maintain a clear focus on CI fundamentals and failure-driven debugging. The pipeline will be extended to support CD in a follow-up project, where the built Docker image will be deployed automatically to a target environment.

More from this blog

codeops-labs.hashnode.dev

16 posts