DEV Community

Cover image for I Built a CI/CD Pipeline with Jenkins, SonarQube, Docker & Kubernetes on AWS EC2
Vishal Patil
Vishal Patil

Posted on

I Built a CI/CD Pipeline with Jenkins, SonarQube, Docker & Kubernetes on AWS EC2

A step-by-step guide to automating build, code quality checks, Docker image push and Kubernetes deployment using Jenkins on AWS EC2."

tags: aws, devops, kubernetes, jenkins

Shipping code by hand is slow and easy to get wrong. So I built a complete CI/CD pipeline on AWS: every push to GitHub is compiled, scanned for code quality, packaged, containerized, pushed to Docker Hub and deployed to Kubernetes automatically.

vishalcloudjourney

🏗️ Architecture

GitHub → Jenkins (EC2) → Maven → SonarQube → Docker → Docker Hub → Kubernetes

Pipeline stages

  1. Declarative: Tool Install
  2. Git Checkout
  3. Code Compile
  4. SonarQube Analysis
  5. Maven Build
  6. Docker Build
  7. Docker Image Push
  8. Deploy to Kubernetes
  9. Declarative: Post Actions

Tools used

AWS EC2, Jenkins, Maven, SonarQube, Docker, Docker Hub, Kubernetes (kubectl), GitHub

Step 1: Launch EC2 instances

  1. Launch an Ubuntu EC2 instance (t2.medium or larger) for Jenkins and SonarQube.
  2. Launch instances for your Kubernetes cluster (or use EKS).
  3. Open these ports in the Security Group:
Port Purpose
22 SSH
8080 Jenkins
9000 SonarQube
6443 Kubernetes API (restrict to the Jenkins server IP)
30000-32767 NodePort range for your app

💡 Avoid opening ports to 0.0.0.0/0 unless you have to. Restrict them to your own IP wherever possible.

Step 2: Install Jenkins

sudo apt update
sudo apt install openjdk-17-jdk -y
# Add the Jenkins repository, then:
sudo apt install jenkins -y
sudo systemctl enable --now jenkins
Enter fullscreen mode Exit fullscreen mode

Open http://<EC2-Public-IP>:8080, unlock Jenkins with the initial admin password and install the suggested plugins.

Step 3: Install Docker, Maven and kubectl

sudo apt install docker.io maven -y
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins
# install kubectl as per the official Kubernetes docs
Enter fullscreen mode Exit fullscreen mode

Adding jenkins to the docker group lets pipeline builds run Docker commands.

Step 4: Set up SonarQube

docker run -d --name sonarqube -p 9000:9000 sonarqube:lts-community
Enter fullscreen mode Exit fullscreen mode
  1. Log in at http://<EC2-IP>:9000.
  2. Generate a token under My Account → Security.
  3. Create a project and note its project key.

Step 5: Configure Jenkins

Plugins: SonarQube Scanner, Docker Pipeline, Kubernetes CLI, Pipeline Stage View, Maven Integration.

Credentials (Manage Jenkins → Credentials):

  • dockerhub-cred: Docker Hub username and password
  • sonar-token: the SonarQube token
  • k8s-config: your kubeconfig file, as a Secret file

Tools (Manage Jenkins → Tools): add Maven and JDK.

SonarQube server (Manage Jenkins → System): add the server URL and token.

Step 6: Write the Jenkinsfile

pipeline {
    agent any
    tools { maven 'maven3' }

    environment {
        IMAGE = "your-dockerhub-user/your-app"
        TAG   = "${BUILD_NUMBER}"
    }

    stages {
        stage('Git Checkout') {
            steps { git branch: 'main', url: 'https://github.com/<you>/<repo>.git' }
        }
        stage('Code Compile') {
            steps { sh 'mvn compile' }
        }
        stage('SonarQube Analysis') {
            steps {
                withSonarQubeEnv('sonar') {
                    sh 'mvn sonar:sonar'
                }
            }
        }
        stage('Maven Build') {
            steps { sh 'mvn clean package -DskipTests' }
        }
        stage('Docker Build') {
            steps { sh "docker build -t $IMAGE:$TAG ." }
        }
        stage('Docker Image Push') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'dockerhub-cred',
                    usernameVariable: 'U', passwordVariable: 'P')]) {
                    sh 'echo $P | docker login -u $U --password-stdin'
                    sh "docker push $IMAGE:$TAG"
                }
            }
        }
        stage('Deploy to Kubernetes') {
            steps {
                withKubeConfig(credentialsId: 'k8s-config') {
                    sh "sed -i 's|IMAGE_TAG|$TAG|g' k8s/deployment.yaml"
                    sh 'kubectl apply -f k8s/deployment.yaml'
                    sh 'kubectl apply -f k8s/service.yaml'
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Kubernetes manifests

Keep these in a k8s/ folder in your repo.

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: your-dockerhub-user/your-app:IMAGE_TAG
          ports:
            - containerPort: 8080
Enter fullscreen mode Exit fullscreen mode

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
Enter fullscreen mode Exit fullscreen mode

Step 8: Run the pipeline

Create a Pipeline job, point it at your repo's Jenkinsfile and click Build Now. The Stage View shows each stage turning green, and the SonarQube Quality Gate shows Passed when your code meets the quality rules.

🛠️ Troubleshooting: when "Deploy to Kubernetes" fails

My first builds (#25, #26 and #27) passed every stage until Deploy to Kubernetes, then failed. Build #25 failed in under a second, while #26 and #27 ran for about 3 minutes before failing. Build #28 passed, and the deploy stage took about 38 seconds.

Since everything before the deploy stage worked, the problem was between Jenkins and the cluster. A failure after about 3 minutes usually points to a connection timeout, and a near-instant failure usually points to a credential or config error. Things to check:

  • Kubeconfig credential: is it uploaded correctly, and does the withKubeConfig ID match?
  • Network access: can the Jenkins server reach the cluster API on port 6443? Check the Security Group and the server IP in the kubeconfig.
  • Manual test: SSH into the Jenkins server and run kubectl get nodes as the jenkins user.
  • Manifest errors: check the YAML, image name and tag.
  • Pod state: run kubectl get pods and kubectl describe pod to spot image pull errors.

📚 Key learnings

  • Automating the path from commit to deployment removes manual mistakes.
  • SonarQube's Quality Gate catches bad code before it ships.
  • Tagging images with the build number makes every deployment traceable and easy to roll back.
  • Most pipeline failures come from credentials, permissions and networking, so check those first.

🚀 What's next

  • Add a GitHub webhook so builds trigger on every push.
  • Move to Amazon EKS and Amazon ECR.
  • Add Slack or SNS notifications for build status.

🤝 Connect with me

If this helped you, drop a ❤️ and share what you're building in the comments!

vishalcloudjourney #AWS #DevOps #Jenkins #Kubernetes #Docker #SonarQube #CICD

Top comments (0)