In this post, we’ll learn how to containerize and deploy a Spring Boot application to a Kubernetes cluster.
· Prerequisites
· Overview
∘ What is Kubernetes?
∘ Kubernetes Cluster Architecture
· Setting up the Kubernetes Cluster
· Create a Spring Boot Application
∘ Project structure
∘ Building Docker Images
· Deploy the Application to Kubernetes
∘ What is a Kubernetes Manifest file?
∘ Kubernetes Deployments and Service
∘ Create a Kubernetes Manifest file
∘ Deploy the container image to Kubernetes
∘ Endpoint testing
· Conclusion
· References
Prerequisites
This is the list of all the prerequisites:
- A Spring Boot project (with Java 17 and Maven 3.8.+)
- kubectl
- A Kubernetes cluster (We’ll use Minikube on our local machine for the first lab.)
- Docker installed
This story starts a new series to learn Kubernetes with Spring Ecosystem.
Overview
What is Kubernetes?
Kubernetes, also known as K8s, is an open-source system for automating the deployment, scaling, and management of containerized applications. It groups containers that make up an application into logical units for easy management and discovery.
Kubernetes Cluster Architecture
A Kubernetes cluster consists of a set of worker machines, called nodes, that run containerized applications. Every cluster has at least one worker node. The worker node(s) host the Pods that are the components of the application workload. The control plane manages the worker nodes and the Pods in the cluster.
Setting up the Kubernetes Cluster
First of all, we need to set up a local Kubernetes cluster. There are several ways to create a Kubernetes cluster. In this story, we will focus on using minikube.
minikube quickly sets up a local Kubernetes cluster on macOS, Linux, and Windows. We proudly focus on helping application developers and new Kubernetes users.
We start a cluster with 2 nodes in the docker driver.
minikube start --nodes 2 -p multinode --driver=docker
And the output must be something similar like:
~$ kubectl get no
NAME STATUS ROLES AGE VERSION
multinode Ready control-plane 24d v1.28.3
multinode-m02 Ready <none> 2m53s v1.28.3
Create a Spring Boot Application
We will create a simple Spring project from start.spring.io, with the following dependencies: Spring Web, Lombok, and Validation.

Project structure
We’ll create a simple book REST API without a Database.
- Book model
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private String id;
private String title;
private String isbn;
private String description;
private Integer page;
private BigDecimal price;
}
- Book repository
@Slf4j
@Repository
public class BookRepository {
private final List<Book> books = new ArrayList<>();
public BookRepository(){
IntStream.range(0, 10).forEach(i -> books.add(Book.builder()
.id(String.valueOf(i))
.isbn("IBN"+i)
.title("TITLE"+i)
.description("Desc"+i)
.page(100+1)
.price(BigDecimal.valueOf(1000L+1))
.build()));
}
public Book save(Book book){
return book;
}
public Book update(Book book){
return book;
}
public Optional<Book> findById(String id){
return books.stream().filter(book -> book.getId().equals(id)).findFirst();
}
public List<Book> findAll(){
return books;
}
public void deleteById(String id){
log.debug("Delete book with id: {}", id);
}
}
- Book Service
@Service
public class BookServiceImpl implements BookService {
private final BookRepository repository;
public BookServiceImpl(BookRepository repo) {
this.repository = repo;
}
/**
* {@inheritDoc}
*/
@Override
public Book create(Book d) {
try {
return repository.save(d);
} catch (Exception ex) {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public Book update(Book d) {
try {
return repository.update(d);
} catch (Exception ex) {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public Book getOne(String id) {
try {
return repository.findById(id).orElse(null);
} catch (Exception ex) {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public List<Book> getAll() {
try {
return repository.findAll();
} catch (Exception ex) {
return Collections.emptyList();
}
}
/**
* {@inheritDoc}
*/
@Override
public void delete(String id) {
repository.deleteById(id);
}
}
- Book controller
@RestController
@RequestMapping("/api/book")
public class BookController {
private final Logger log = LoggerFactory.getLogger(BookController.class);
private final BookService entityService;
public BookController(BookService entityService) {
this.entityService = entityService;
}
/**
* {@code POST /book} : Create a new book.
*
* @param book the book to create.
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new book.
*/
@PostMapping()
public ResponseEntity<Book> createBook(@RequestBody @Valid Book book) {
log.debug("REST request to save Book : {}", book);
return new ResponseEntity<>(entityService.create(book), HttpStatus.CREATED);
}
/**
* {@code PUT /book} : Updates an existing book.
*
* @param book the book to update.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated book,
* or with status {@code 400 (Bad Request)} if the book is not valid,
* or with status {@code 500 (Internal Server Error)} if the book couldn't be updated.
*/
@PutMapping()
public ResponseEntity<Book> updateBook(@Valid @RequestBody Book book) {
log.debug("REST request to update Book : {}", book);
Book result = entityService.update(book);
return ResponseEntity.ok().body(result);
}
/**
* {@code GET /book} : get all the books.
*
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of book in body.
*/
@GetMapping()
public ResponseEntity<List<Book>> getAllBook() {
log.debug("REST request to get all books");
List<Book> lst = entityService.getAll();
return new ResponseEntity<>(lst,HttpStatus.OK);
}
/**
* {@code GET /book/:id} : get the "id" book.
*
* @param id the id of the book to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the book, or with status {@code 404 (Not Found)}.
*/
@GetMapping(value = "/{id}")
public ResponseEntity<Book> getOneBook(@PathVariable("id") String id) {
log.debug("REST request to get Book : {}", id);
Book e = entityService.getOne(id);
return new ResponseEntity<>(e, HttpStatus.OK);
}
/**
* {@code DELETE /book/:id} : delete the "id" book.
*
* @param id the id of the book to delete.
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteBook(@PathVariable("id") String id) {
log.debug("REST request to delete Book : {}", id);
entityService.delete(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
This is the final folders & files structure for our project:

Let’s try:


It’s working. Finally, let’s create a runnable jar.
$ mvn clean package
Building Docker Images
There are multiple options for containerizing a Spring Boot application. We use Dockerfile, a text document containing all the commands a user can call on the command line to assemble an image.
Add a Dockerfile at the root of the project.
FROM eclipse-temurin:17-jre-alpine
ADD target/spring-boot-k8s*.jar spring-boot-k8s.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","spring-boot-k8s.jar"]
FROMCreate a new build stage from a base image. In this case, we use eclipse-temurin image.ADDAdd the generated jar file from the target folder to the docker image.EXPOSEIndicate the port for our API.ENTRYPOINTexecutesjava -jarinside the container.
Create the image locally on all nodes in the cluster
$ minikube image build -t spring-boot-k8s . --all
Deploy the Application to Kubernetes
What is a Kubernetes Manifest file?
A Kubernetes Manifest file is a YAML or JSON file that describes the desired state of a Kubernetes object. These objects can include deployment, replica set, service, and more. Manifest files define the specifications of the object, such as its metadata, properties, and desired state.
Kubernetes Deployments and Service
A Kubernetes deployment is a resource object in Kubernetes that provides declarative updates to applications. A deployment allows you to describe an application’s life cycle, such as which images to use for the app, the number of pods there should be, and how they should be updated. A Deployment file is used to create, update, or delete Deployments in a Kubernetes cluster.
In Kubernetes, a Service is a method for exposing a network application that is running as one or more Pods in your cluster. Each Service object defines a logical set of endpoints (usually these endpoints are Pods) along with a policy about how to make those pods accessible.
Create a Kubernetes Manifest file
Many applications require multiple resources to be created, such as a Deployment along with a Service. Management of multiple resources can be simplified by grouping them together in the same file (separated by — — in YAML).
We need to create a deployment configuration file. This file contains two sections (Service and Deployment)
apiVersion: v1 # Kubernetes API version
kind: Service # Kubernetes resource kind
metadata:
name: book-api-service
spec:
selector:
app: backend
ports:
- protocol: TCP # The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
port: 8081 # The port that will be exposed by this service.
targetPort: 8080 # The port to access on the pods targeted by the service.
type: NodePort # type of the service.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-book-api #The name of deployment
spec:
replicas: 2 # Number of replicas that will be created for this deployment
selector: #Label selector for pods.
matchLabels:
app: backend
environment: dev
template:
metadata:
labels:
app: backend
environment: dev
spec:
containers:
- name: book-api #container name
image: spring-boot-k8s:latest
ports:
- containerPort: 8080
imagePullPolicy: Never #the kubelet does not try fetching the image. If the image is somehow already present locally, the kubelet attempts to start the container; otherwise, startup fails.
Deploy the container image to Kubernetes
Now, we can apply the configuration by using the following command:
$ kubectl apply -f k8s.yaml
service/book-api-service created
deployment.apps/backend-book-api created
Check that the application is running:

Let’s open the Kubernetes dashboard running in the minikube cluster.
$ minikube dashboard

As we see, all resources are working well.
Endpoint testing
Retrieves the IP address of the cluster.
$ minikube ip
192.168.58.2


Conclusion
Well done !!. In this post, We have seen how to containerize and deploy a Spring Boot application to a Kubernetes cluster.
The complete source code is available on GitHub.
You can reach out to me and follow me on Medium, Twitter, GitHub, Linkedln
Support me through GitHub Sponsors.
Thanks for reading!
