A RESTful Task Management API built with Spring Boot, deployed through a fully automated CI/CD pipeline — every git push to main results in a live, updated deployment on Azure with zero manual steps.
Status: ✅ Live and fully operational. The pipeline has been verified end-to-end through multiple real deployments, including debugging and resolving SSH authentication, Java version mismatches, and health-check endpoint issues along the way.
This is an end-to-end DevOps pipeline covering the full path from source code to a running production service:
git push → GitHub webhook → Jenkins → Maven build & test
→ Docker image build & push → Ansible deploy → Live on Azure VM
Every stage is automated. A single git push triggers the entire chain, including build verification, containerisation, and a health-check-gated deployment.
| Component | Role |
|---|---|
| Spring Boot + H2 | The application — REST API with JPA persistence |
| Maven | Compiles the code, runs JUnit tests, packages the .jar |
| Docker | Containerises the built .jar into a portable image |
| Docker Hub | Stores versioned images, tagged by Jenkins build number |
| Jenkins | Orchestrates the pipeline — triggered by a GitHub webhook on every push |
| Ansible | SSHes into the production VM, pulls the new image, and redeploys the container |
| Azure VMs | Two Ubuntu 22.04 VMs — one running Jenkins, one running the live app |
Two Azure VMs are used:
- jenkins-server — runs Jenkins, Docker, and Ansible (the CI/CD control plane)
- prod-server — runs the actual application container, reachable on port 8080
graph LR
A["Developer"] -->|git push| B["GitHub"]
B -->|webhook| C["Jenkins Server VM"]
C -->|mvn build + test| D["JAR"]
D -->|docker build + push| E["Docker Hub"]
C -->|ansible-playbook| F["Prod Server VM"]
F -->|docker pull + run| G["Task Manager API :8080"]
task-manager-cicd/
├── src/main/java/com/devops/taskmanager/
│ ├── Task.java # JPA entity — id, title, completed
│ ├── TaskRepository.java # Spring Data JPA repository
│ ├── TaskController.java # REST controller — GET, POST, PUT, DELETE on /tasks
│ └── HealthController.java # /health → {"status": "UP"}
├── src/main/resources/
│ └── application.properties # H2 in-memory DB config
├── Dockerfile # eclipse-temurin:21-jre-alpine base image
├── Jenkinsfile # 5-stage declarative pipeline
├── ansible/
│ ├── deploy.yml # Deployment playbook
│ └── inventory # Production host definition
└── README.md
| Method | Endpoint | Description |
|---|---|---|
GET |
/tasks |
Fetch all tasks |
POST |
/tasks |
Create a new task |
PUT |
/tasks/{id} |
Update / mark a task complete |
DELETE |
/tasks/{id} |
Delete a task |
GET |
/health |
Health check — used by Ansible to verify deployment |
The Jenkinsfile defines five stages, run in order on every push to main:
- Checkout — pulls the latest code from GitHub
- Build —
mvn clean package -DskipTests - Test —
mvn test, results published via JUnit reporting - Docker Build & Push — builds the image, tags it with the Jenkins build number, pushes to Docker Hub
- Deploy — runs the Ansible playbook over SSH, which pulls the new image, replaces the running container, and polls
/healthuntil it returns200 OK
If any stage fails, the pipeline stops and the previous deployment on prod-server is left untouched — there's no partial-deploy risk since the old container only gets removed after the new image has already been pulled successfully.
On both success and failure, the pipeline's post block sends a Slack notification and an email with the build status and a link to the console output — so a deployment failure is known immediately, not discovered later.
- Backend: Spring Boot 3.5.0, Spring Data JPA, H2 (in-memory)
- Language runtime: Java 21 (build and container runtime matched)
- Testing: JUnit 5
- CI/CD: Jenkins (Declarative Pipeline)
- Notifications: Slack (Incoming Webhooks) + Email (Email Extension Plugin), triggered on build success/failure
- Containerisation: Docker (
eclipse-temurin:21-jre-alpine), Docker Hub - Infrastructure Automation: Ansible (agentless, SSH-based)
- Cloud: Microsoft Azure (Ubuntu 22.04 VMs, Standard_B1s/B2s)
mvn clean package
java -jar target/task-manager-0.0.1-SNAPSHOT.jarThe app starts on http://localhost:8080.
curl http://localhost:8080/health
# { "status": "UP" }
curl http://localhost:8080/tasks
# []mvn clean package -DskipTests
docker build -t task-manager:local .
docker run -p 8080:8080 task-manager:local- Jenkins job is configured as a Pipeline job, pointed at this repo's
Jenkinsfilevia "Pipeline script from SCM" - A GitHub webhook (
/github-webhook/) notifies Jenkins on every push tomain - Docker Hub credentials and the production server's SSH key are stored in Jenkins' credential manager — never hardcoded in the pipeline
- The Ansible inventory (
ansible/inventory) points at the production VM, using key-based SSH authentication - Deployment is verified automatically — the pipeline only reports success once
/healthresponds with200 OKon the production server - Build status is pushed to a Slack channel via an Incoming Webhook (configured as a Jenkins Secret text credential) and to email via the Email Extension plugin, both triggered from the pipeline's
postblock
- Writing declarative Jenkins pipelines and wiring them to GitHub via webhooks
- Managing SSH key-based authentication between Jenkins and remote servers — including diagnosing a malformed private key (missing
BEGIN/ENDmarkers from a copy-paste error) that caused alibcryptoparsing failure inssh-add - Resolving a
UnsupportedClassVersionErrorcaused by a mismatch between the JDK used to compile the JAR (Java 21) and the JRE in the Docker base image (originally Java 17) — fixed by aligning both to Java 21 - Diagnosing a Docker container crash loop using
docker ps -aanddocker logs, rather than assuming the deployment itself had failed - Catching a health-check endpoint mismatch (
/actuator/healthvs. the app's actual custom/healthendpoint) that caused the pipeline to fail even after a successful deployment - Using Ansible for agentless, idempotent deployment automation, with
ignore_errorsfor safe first-time deploys and a retry-basedurihealth check to gate pipeline success on the app actually being reachable - Structuring a CI/CD pipeline so failures fail safely without taking down the existing production deployment
Built as a hands-on DevOps capstone project covering Jenkins, Maven, Docker, Ansible, and Azure.