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
- Architecture Overview
- Prerequisites
- Step 1: Create Azure VM
- Step 2: Install Docker
- Step 3: Run Jenkins with Docker Access
- Step 4: Prepare Your Spring Boot Project
- Step 5: Create the Jenkinsfile
- Step 6: Create Jenkins Pipeline Job
- Step 7: Add GitHub Webhook
- Step 8: Open Azure Firewall Ports
- Step 9: Test the Full Pipeline
- Quick Reference
- 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 │ │ │
│ │ └────────────────────────┘ │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
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)
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
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
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
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
Get the initial admin password:
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
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
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"]
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:
application.properties
spring.datasource.url=jdbc:postgresql://postgres:5432/demo
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
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'
}
}
}
Step 6: Create Jenkins Pipeline Job
-
New Item → name it
demo-pipeline→ select Pipeline - Under Build Triggers, check GitHub hook trigger for GITScm polling
- Under Pipeline, choose Pipeline script from SCM
- SCM: Git
- Repository URL: your GitHub repo URL
- Branch:
*/mainor*/dev - Script Path:
Jenkinsfile
- Save
Step 7: Add GitHub Webhook
- In your GitHub repo → Settings → Webhooks → Add webhook
- Set:
Payload URL: http://YOUR_VM_IP:8080/github-webhook/
Content type: application/json
Events: Just the push event ✅
- 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 → Networking → Add 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
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"}
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
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)