DEV Community

Md. Monowarul Amin
Md. Monowarul Amin

Posted on • Edited on

Complete Guide: Jenkins CI/CD Pipeline for Spring Boot with Docker & Microsoft Azure

Jenkins CI/CD Pipeline for Spring Boot with Docker

Push to GitHub → Jenkins builds → App deploys automatically 🚀

Overview

Setting up a CI/CD pipeline can feel overwhelming, but the core idea is simple: every time you push code, something should automatically build, test, and deploy it for you — no manual steps.

In this guide, we'll set up exactly that using Jenkins, Docker, and Azure. Jenkins runs inside a Docker container on an Azure VM, listens for pushes via a GitHub webhook, builds your Spring Boot app into a Docker image, and deploys it alongside a PostgreSQL database using Docker Compose. By the end, a single git push will trigger the entire pipeline and have your app live within minutes.


Table of Contents

  1. Architecture Overview
  2. Prerequisites
  3. Step 1: Create Azure VM
  4. Step 2: Install Docker
  5. Step 3: Run Jenkins with Docker Access
  6. Step 4: Prepare Your Spring Boot Project
  7. Step 5: Create the Jenkinsfile
  8. Step 6: Create Jenkins Pipeline Job
  9. Step 7: Add GitHub Webhook
  10. Step 8: Open Azure Firewall Ports
  11. Step 9: Test the Full Pipeline
  12. Quick Reference
  13. Troubleshooting

Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                    Azure VM (Ubuntu)                     │
│                                                          │
│  ┌────────────────┐  ┌──────────────────────────────┐  │
│  │  Jenkins       │  │  Application Stack           │  │
│  │  Container     │  │                              │  │
│  │  Port: 8080    │  │  ┌────────────────────────┐  │  │
│  │                │  │  │  Spring Boot App       │  │  │
│  │  - Build       │──┼─▶│  Container             │  │  │
│  │  - Test        │  │  │  Port: 8081            │  │  │
│  │  - Deploy      │  │  └────────────────────────┘  │  │
│  │                │  │             │                 │  │
│  │  Docker-in-    │  │             ▼                 │  │
│  │  Docker        │  │  ┌────────────────────────┐  │  │
│  └────────────────┘  │  │  PostgreSQL Container  │  │  │
│                      │  │  Port: 5432            │  │  │
│                      │  └────────────────────────┘  │  │
│                      └──────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

How Automated Builds Work

Developer         GitHub            Jenkins           Docker
    │                │                  │                │
    │  git push      │                  │                │
    ├───────────────>│                  │                │
    │                │  Webhook POST    │                │
    │                ├─────────────────>│                │
    │                │                  │  Build JAR     │
    │                │  git pull        │  docker build  │
    │                │<─────────────────┤───────────────>│
    │                │                  │  docker-compose│
    │                │                  │  up -d         │
    │                │                  ├───────────────>│
    │                │                  │  App Running ✅ │

⏱️ Total Time: ~3-5 minutes (fully automatic)
Enter fullscreen mode Exit fullscreen mode

Prerequisites

  • Azure account
  • Basic knowledge of Linux, Git, Docker, and Spring Boot
  • A GitHub repository with your Spring Boot project

Step 1: Create Azure VM

Create an Ubuntu 24.04 VM (Standard_B2s — 2 vCPU, 4GB RAM minimum) and SSH into it.

ssh azureuser@YOUR_VM_IP
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Docker

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo usermod -aG docker $USER
newgrp docker

# Note the docker group GID — you'll need it in the next step
getent group docker
# Output: docker:x:114:azureuser  ← 114 is the GID
Enter fullscreen mode Exit fullscreen mode

Step 3: Run Jenkins with Docker Access

docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --group-add 114 \               # ← replace with your docker GID
  --restart=unless-stopped \
  jenkins/jenkins:lts-jdk21
Enter fullscreen mode Exit fullscreen mode

Why mount the Docker socket? This lets Jenkins run Docker commands on the host machine — no separate Docker daemon needed inside the container.

Then install Docker CLI inside Jenkins:

docker exec -it -u root jenkins bash
apt-get update && apt-get install -y docker.io docker-compose
chmod 666 /var/run/docker.sock
exit
Enter fullscreen mode Exit fullscreen mode

Get the initial admin password:

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Enter fullscreen mode Exit fullscreen mode

Access Jenkins at http://YOUR_VM_IP:8080, complete setup, and install suggested plugins.


Step 4: Prepare Your Spring Boot Project

Your project should have this structure:

your-project/
├── src/
├── build.gradle
├── Dockerfile
├── docker-compose.yml
└── Jenkinsfile
Enter fullscreen mode Exit fullscreen mode

Dockerfile (Multi-stage)

FROM gradle:9.3-jdk21-corretto AS builder
WORKDIR /app
COPY . .
RUN ./gradlew clean bootJar --no-daemon

FROM eclipse-temurin:21-jdk
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

version: '3.9'
services:
  app:
    build: .
    container_name: springboot-app
    depends_on:
      - postgres
    ports:
      - "8081:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/demo
      SPRING_DATASOURCE_USERNAME: postgres
      SPRING_DATASOURCE_PASSWORD: password
    restart: unless-stopped

  postgres:
    image: postgres:15
    container_name: demo-postgres
    environment:
      POSTGRES_DB: demo
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  postgres-data:
Enter fullscreen mode Exit fullscreen mode

application.properties

spring.datasource.url=jdbc:postgresql://postgres:5432/demo
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
Enter fullscreen mode Exit fullscreen mode

Step 5: Create the Jenkinsfile

pipeline {
    agent any

    environment {
        DOCKER_IMAGE = "springboot-app"
        DOCKER_TAG   = "${BUILD_NUMBER}"
    }

    stages {
        stage('Checkout') {
            steps { checkout scm }
        }

        stage('Build') {
            steps {
                sh '''
                    chmod +x gradlew
                    ./gradlew clean bootJar -x test --no-daemon
                '''
            }
        }

        stage('Docker Build') {
            steps {
                sh '''
                    docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} .
                    docker tag  ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest
                '''
            }
        }

        stage('Deploy') {
            steps {
                sh '''
                    docker-compose down || true
                    docker-compose up -d --build
                '''
            }
        }

        stage('Health Check') {
            steps {
                sh '''
                    sleep 15
                    curl -f http://localhost:8081/actuator/health || echo "Health endpoint not available"
                '''
            }
        }
    }

    post {
        success { echo '✅ Deployed! App running at http://localhost:8081' }
        failure {
            sh 'docker-compose logs --tail=50 || true'
        }
        always {
            sh 'docker image prune -f || true'
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Create Jenkins Pipeline Job

  1. New Item → name it demo-pipeline → select Pipeline
  2. Under Build Triggers, check GitHub hook trigger for GITScm polling
  3. Under Pipeline, choose Pipeline script from SCM
    • SCM: Git
    • Repository URL: your GitHub repo URL
    • Branch: */main or */dev
    • Script Path: Jenkinsfile
  4. Save

Step 7: Add GitHub Webhook

  1. In your GitHub repo → SettingsWebhooksAdd webhook
  2. Set:
   Payload URL:  http://YOUR_VM_IP:8080/github-webhook/
   Content type: application/json
   Events:       Just the push event ✅
Enter fullscreen mode Exit fullscreen mode
  1. Save — you should see a green checkmark confirming Jenkins received the ping.

Can't reach Jenkins? If your VM is behind a firewall, use ngrok to create a public tunnel: ngrok http 8080, then use the ngrok URL as the webhook payload URL.


Step 8: Open Azure Firewall Ports

In Azure Portal → your VM → NetworkingAdd inbound port rule:

Port Purpose
8080 Jenkins UI
8081 Spring Boot App

Step 9: Test the Full Pipeline

echo "# trigger build" >> README.md
git add . && git commit -m "test: trigger CI"
git push origin dev
Enter fullscreen mode Exit fullscreen mode

Within seconds, Jenkins picks up the push and starts building. Monitor at http://YOUR_VM_IP:8080/job/demo-pipeline/.

Once complete, verify your app:

curl http://YOUR_VM_IP:8081/actuator/health
# Expected: {"status":"UP"}
Enter fullscreen mode Exit fullscreen mode

Quick Reference

URL Purpose
http://VM_IP:8080 Jenkins dashboard
http://VM_IP:8081 Spring Boot app
http://VM_IP:8081/actuator/health Health check
http://VM_IP:8081/swagger-ui/index.html Swagger UI

Handy Commands

# View running containers
docker ps

# Live app logs
docker logs springboot-app -f

# Restart app
docker-compose restart

# Full redeploy
docker-compose down && docker-compose up -d

# Emergency cleanup
docker system prune -af
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Problem Fix
docker: not found in Jenkins Run apt-get install -y docker.io docker-compose inside Jenkins container as root
permission denied on Docker socket chmod 666 /var/run/docker.sock inside Jenkins container
Webhook not triggering builds Check Recent Deliveries tab on GitHub webhook page; verify port 8080 is open in NSG
Connection refused on DB Use service name postgres in JDBC URL, not localhost
Port already allocated Change host port in docker-compose.yml (e.g., 8082:8080)

Alhamdulillah — May this guide benefit you! 🚀

Top comments (0)