Introduction
In this tutorial we will be running a Spring boot application & open source database PostreSql. We will be using docker compose to run multiple containers at once.
Requirements
- Docker (docker-compose)
- Jdk
- Maven
- IDE (Intellij or VScode)
Spring boot application
In this segment we will create a basic Spring boot app from Spring Initializer. Add the below dependencies:
- Spring Data JPA
- Spring Web
- PostgreSql driver
Download the generated zip file & load it into your IDE. You can use this github project also.
We need to setup Database configuration in application.yaml file.
spring:
datasource:
url: jdbc:postgresql://${POSTGRES_URL}:5432/${POSTGRES_DB}
username: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
driver-class-name: org.postgresql.Driver
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
We are using variables for Database here. We will externalize these variables in docker-compose.yaml
file.
If we try to run the application, it will give us an error since there is no database setup.
Building the Application Dockerfile
Lets create a Dockerfile
in root of spring boot project
The contents of the file will be something like
FROM amazoncorretto:17-alpine-jdk
# Setting work directory
WORKDIR /app
# Install Maven
RUN apk add --no-cache maven
# Copy your project files and build the project
COPY . .
RUN mvn clean install
ENTRYPOINT ["java","-jar","target/docker-postgres-spring-boot-1.0.jar"]
We are setting up
maven
so that it automatically clean installs the application which creates jar in target. With this, we do not need to copy jar file from target every time.
Postgres Dockerfile
Lets create Dockerfie for PosgreSql. We can create all this configuration in docker-compose.yaml
file but it becomes really easy once we segregate.
On the root, lets create a folder db-postgres
which will contain
- Dockerfile
- data.sql file
- postgres-data (folder for docker volume)
FROM postgres:14.7
RUN chown -R postgres:postgres /docker-entrypoint-initdb.d/
We will be creating some tables and adding some reference data as a part of container initialization in create.sql
CREATE SCHEMA IF NOT EXISTS "public";
CREATE TABLE "public".employee (
id SERIAL PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
department VARCHAR(100),
position VARCHAR(100),
salary DECIMAL(10, 2),
hire_date DATE
);
-- Insert sample records into the 'employee' table
INSERT INTO employee (first_name, last_name, email, department, position, salary, hire_date)
VALUES
('John', 'Doe', 'johndoe@example.com', 'Engineering', 'Software Developer', 75000.00, '2020-01-15'),
('Jane', 'Smith', 'janesmith@example.com', 'Marketing', 'Marketing Manager', 65000.00, '2019-08-01'),
('Emily', 'Jones', 'emilyjones@example.com', 'Human Resources', 'HR Coordinator', 55000.00, '2021-05-23');
Below is the folder structure
for the entire project
├── docker-compose.yaml
├── db-postgres/
| ├── Dockerfile
│ ├── data.sql (DDL file)
│ └── postgres-data/ (docker volume)
└── docker-postgres-spring-boot/ (application root)
├── Dockerfile
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── your.package/
│ ├── resources/
│ │ ├── application.yml
└── ... (other project files)
Docker compose file
Lets create the final docker-compose.yaml file. There are 2 services in the docker compose file
- Spring boot app
- Postgres DB
version: "3.8"
services:
db:
build:
context: ./db-postgres
dockerfile: Dockerfile
ports:
- "5432:5432"
volumes:
- ./db-postgres/postgres-data:/var/lib/postgresql/data
- ./db-postgres/create.sql:/docker-entrypoint-initdb.d/create.sql
env_file:
- .env
app:
depends_on:
- db
build:
context: ./docker-postgres-spring-boot
dockerfile: Dockerfile
ports:
- "8080:8080"
env_file:
- .env
We are not passing individual environment variables, rather we are using --env-file argument in docker-compose.yaml
create a file .env
at the root (same directory as docker-compose.yaml file). Add the following variables.
POSTGRES_DB=employeedb
POSTGRES_PASSWORD=mysecretpassword
POSTGRES_URL=host.docker.internal. # or use db(container-name)
POSTGRES_USER=postgres
HOST_URL=host.docker.internal
POSTGRES_URL=host.docker.internal
is an internal docker host which is used.
MacOS - if nothing works usehost.docker.internal
Windows/linux/WSL - We can use container-name -db
The docker-compose.yaml is located at the root.
Lets go through the file now
Spring boot app
- The Spring boot app uses a Dockerfile located on the root of the spring boot project folder specified by
context: ./docker-postgres-spring-boot
- We have exposed PORT 8080 on the host machine
- We are using
--env-file
attribute to declare environment variables
Postgres DB
- The postgres uses Dockerfile inside
postgres-db
folder specified by context - We are using volumes to have the data
persisted
on every restart of the containers - With volume we are placing
create.sql
in the container & usingpostgres-data
as mount folder on host machine. This folder will contain data.
volumes:
- ./db-postgres/postgres-data:/var/lib/postgresql/data
- ./db-postgres/create.sql:/docker-entrypoint-initdb.d/create.sql
Running Docker compose
We will be running the services in docker-compose using the command
docker-compose up
Check the logs. If everything runs fine, we should see logs like this
db-1 | 2024-02-08 01:49:44.444 UTC [1] LOG: database system is ready to accept connections
app-1 | 2024-02-08T01:49:47.090Z INFO 1 --- [ main] .p.d.DockerPostgresSpringBootApplication : Started DockerPostgresSpringBootApplication in 2.949 seconds (process running for 3.513)
To stop the apps we can use the below command
docker-compose down
Creating employee controller
We will create an API to get data from the employee
table which was created as part of containerization.
Employee Controller
@RestController
@RequestMapping("employee")
public class EmployeeController {
@Autowired
EmployeeRepository employeeRepository;
@GetMapping("{email}")
public ResponseEntity<?> authenticate(@PathVariable String email) {
return ResponseEntity.ok(employeeRepository.findByEmail(email));
}
@PostMapping
public ResponseEntity<?> register(@RequestBody Employee request){
employeeRepository.save(EEmployee.builder()
.email(request.getEmail())
.firstName(request.getFirstName())
.lastName(request.getLastName())
.salary(request.getSalary())
.department(request.getDepartment())
.position(request.getPosition())
.hireDate(LocalDate.now())
.build());
return ResponseEntity.created(URI.create("/api/employee")).build();
}
}
We have created employee controller with 2 endpoint:
GET /api/employee/email
POST /api/employee
Creating Employee entity and Repository
Lets create a simple EEmployee
class entity
// Lombok annotations
@Table(name = "employee")
public class EEmployee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
@Column(name = "first_name", nullable = false, length = 50)
String firstName;
@Column(name = "last_name", nullable = false, length = 50)
String lastName;
@Column(name = "email", nullable = false, length = 100)
String email;
@Column(name = "department", nullable = false, length = 100)
String department;
@Column(name = "position", nullable = false, length = 100)
String position;
@Column(name = "salary", nullable = false)
double salary;
@Column(name = "hire_date", nullable = false)
LocalDate hireDate;
}
Below is the Employee Repository
@Repository
public interface EmployeeRepository extends JpaRepository<EEmployee, Integer> {
Optional<EEmployee> findByEmail(String email);
}
For simplicity purposes we are calling repository directly from the controller
Running Docker compose again
Lets run docker compose with updated code. We do not need to copy target file to docker context.
docker-compose up
We can also remove the docker images if code is not updating
docker images
docker rmi <image-name-or-id>
Lets create an employee using POST /api/employee
curl -X POST http://localhost:8080/api/employee \
-H "Content-Type: application/json" \
-d '{
"firstName": "Mike",
"lastName": "Thomas",
"email": "mike@thomas.com",
"department": "Sales",
"position": "Software Developer",
"salary": 123000.0,
"hireDate": "2019-01-15"
}'
Lets try to fetch employee using GET /api/employee/{email}
curl --location 'localhost:8080/api/employee/johndoe@example.com'
Top comments (0)