Migrating an Springboot Java API: Part 1 - Docker
Tags: java, docker, azure, aws
Published: 2023-11-04 20:21:17

Despite being a primarily .NET Core Azure developer, sometimes we take new challenges. I want to write my process of getting an old Java API from no CI/CD into AWS, in hope that helps devs in a similar situation.

> I am aware many things I do in this tutorial can be improved, but being a beginner in AWS and coming from a different tech stack means I will take some decisions which the future me will frown upon, but bear with me ;)

## Pre-requisites
- [Docker](https://www.docker.com/)
- IDE that supports Java and Springboot. I used [IntelliJ IDEA](https://www.jetbrains.com/idea/)
- JDK (in my case 1.8)
- Springboot
- A database (in my case MySql)

## The original setup
This application had no CI/CD pipeline. We could access only an AWS EC2 instance with the running APIs amd the source code.

The original setup is a Java 1.8 solution with 3 projects:

```
MyJavaSolution/
├── pom.xml
│
├── admin/
│ │── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ ├── resources/
│ │ │ └──── application.yml
│ │ └── pom.xml
│
├── customer/
│ │── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ ├── resources/
│ │ │ └──── application.yml
│ │ └── pom.xml
│
├── common/
│ │── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ ├── resources/
│ │ │ └──── application.yml
│ │ └── pom.xml
```

Both the APIs rely on the `common` project. And both APIs use a Redis cache (which we will include in the `docker-compose`)

## The objective
The final objective is having a fully functioning CI/CD pipeline and deployment to a cloud provider. 

For this series I will use Azure DevOps for the pipeline and AWS as hosting provider.

## Part 1 - Dockerizing the project
The first step I did was dockerizing the application. That allows me to reduce the variability between environments and builds, and to access services such as AWS Elastic Beanstalk.

### The APIs
Without changing any of the actual solution, I created the following Docker files:
- Customer.Dockerfile
```dockerfile
# Use a base image with Java 8 and Maven for building
FROM maven:3.6.3-jdk-8 AS builder

WORKDIR /app/common
COPY ./common/pom.xml ./pom.xml
COPY ./common/src ./src

WORKDIR /app/admin
COPY ./admin/pom.xml ./pom.xml
COPY ./admin/src ./src

WORKDIR /app/customer
COPY ./customer/pom.xml ./pom.xml
COPY ./customer/src ./src

WORKDIR /app
COPY ./pom.xml ./pom.xml
RUN mvn dependency:go-offline

RUN mvn package

FROM openjdk:8-jdk-alpine AS runner
WORKDIR /app
COPY --from=builder app/common/target/common-0.0.1-SNAPSHOT.jar ./common.jar
COPY --from=builder app/customer/target/customer-0.0.1-SNAPSHOT.jar ./customer.jar

EXPOSE 9001

CMD ["java", "-jar", "customer.jar"]

```

- Admin.Dockerfile
```dockerfile
# Use a base image with Java 8 and Maven for building
FROM maven:3.6.3-jdk-8 AS builder

WORKDIR /app/common
COPY ./common/pom.xml ./pom.xml
COPY ./common/src ./src

WORKDIR /app/admin
COPY ./admin/pom.xml ./pom.xml
COPY ./admin/src ./src

WORKDIR /app/customer
COPY ./customer/pom.xml ./pom.xml
COPY ./customer/src ./src

WORKDIR /app
COPY ./pom.xml ./pom.xml
RUN mvn dependency:go-offline

RUN mvn package

FROM openjdk:8-jdk-alpine AS runner
WORKDIR /app
COPY --from=builder app/common/target/common-0.0.1-SNAPSHOT.jar ./common.jar
COPY --from=builder app/admin/target/admin-0.0.1-SNAPSHOT.jar ./admin.jar

EXPOSE 9002

CMD ["java", "-jar", "admin.jar"]
```

It might seem a bit strange that I included the `customer/` folder in the Admin.Dockerfile and vice-versa but the reason was because the solution `pom.xml` relied on both to create the `.jar` files and it the refactoring of this issue was not part of the scope.



### The `docker-compose`
After creating the dockerfiles and ensuring they work individually, same as if we running it with Springboot. I then created a `docker-compose` to ensure me and my team could run it easily:

```yaml
version: '3'
services:
  local-redis:
    image: redis:7.2.1
    ports:
      - "6379:6379"
  test-customer:
    build:
      context: .
      dockerfile: Customer.Dockerfile
    depends_on:
      - local-redis
    ports:
      - "9001:9001"
  test-admin:
    build:
      context: .
      dockerfile: Admin.Dockerfile
    depends_on:
      - local-redis
    ports:
      - "9002:9002"
```

### Redis cache
Having now included the Redis cache as a Docker container running alongside the APIs, I just needed to change it's reference in the `application.xml`s.

```yaml
spring:
  application:
    name: MyJavaSolution
# The redis cache should have the same host name and port as the one defined in the docker-compose.yml
  redis:
    host: local-redis
    port: 6379
    timeout: 5000
```


And with that we have a fully working, OS-agnostic way of building and running this 2 Java 1.8 APIs.
In the [next post](https://dev.to/dev-laura/migrating-an-springboot-java-api-part-2-azure-build-3m94) of this series I will show how to use Azure DevOps pipelines to build and deploy this.