Skip to main content

Building and Running Container Images with Java Application Using Docker

Objective

In the first lab you are going to build the application locally. We are leveraging Amazon Corretto a no-cost, multi-platform, production-ready distribution of the Open Java Development Kit (OpenJDK). Corretto comes with long-term support that will include performance enhancements and security fixes. Amazon runs Corretto internally on thousands of production services and Corretto is certified as compatible with the Java SE standard.

Prerequisites

1. Building the application locally

Navigate to the application folder and build the application via Maven:

cd ~/environment/unicorn-store-spring
mvn clean package && mv target/store-spring-1.0.0-exec.jar store-spring.jar

2. Running the application locally

To run the Java Application we need to get a valid database connection-url and password. The database url is stored in AWS Systems Manager Parameter Store and the database password in AWS Secrets Manager. The values will be provided to the application as environment variables.

Set the environment variables from Secrets Manager and Parameter Store:

export SPRING_DATASOURCE_URL=$(aws ssm get-parameter --name databaseJDBCConnectionString | jq --raw-output '.Parameter.Value')
export SPRING_DATASOURCE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id unicornstore-db-secret | jq --raw-output '.SecretString' | jq -r .password)

Start the Java application locally (listening on port 8080):

java -jar -Dserver.port=8080 store-spring.jar

Open another terminal window and test the application via curl:

export SVC_URL=localhost:8080
curl --location --request POST $SVC_URL'/unicorns' --header 'Content-Type: application/json' --data-raw '{
"name": "'"Something-$(date +%s)"'",
"age": "20",
"type": "Animal",
"size": "Very big"
}' | jq
info

Keep this terminal window open. We will use it in subsequent steps.

You should see the following output:

test-success

Switch back to the Java application terminal window, and stop the application with Ctrl+C.

3. Containerizing the application

The local build can be a good starting point, but in the real world, another development machine might miss some of the required dependencies to run the application. To solve the "works okay on my machine" problem, we can use containers.

A container is a standardized unit of software development that holds everything that your software application requires to run. This includes relevant code, runtime, system tools, and system libraries.

Containers are created from a read-only template that's called an image. Images are typically built from a Dockerfile. A Dockerfile is a plaintext file that specifies all of the components that are included in the container. After they're built, these images are stored in a registry such as Amazon ECR where they can be uploaded and downloaded.

Copy a simple Dockerfile to the current application folder:

cd ~/environment/unicorn-store-spring
cp dockerfiles/Dockerfile_00_initial Dockerfile

Inspect the Dockerfile. You will see that we just copy our previously created jar file into the container. We are adding a dedicated user and group (1000) to use the least privilege identity (read more about this in our Well-Architected Container Build Lens).

/unicorn-store-spring/Dockerfile
FROM public.ecr.aws/docker/library/maven:3.9-amazoncorretto-17-al2023 as builder

RUN yum install -y shadow-utils

COPY store-spring.jar store-spring.jar

RUN groupadd --system spring -g 1000
RUN adduser spring -u 1000 -g 1000

USER 1000:1000

EXPOSE 8080
ENTRYPOINT ["java","-jar","-Dserver.port=8080","/store-spring.jar"]

Build a container image:

docker buildx build --load -t unicorn-store-spring:latest .

List previously built container images:

docker images

Run the container instance locally:

docker run \
-e SPRING_DATASOURCE_URL=$SPRING_DATASOURCE_URL \
-e SPRING_DATASOURCE_PASSWORD=$SPRING_DATASOURCE_PASSWORD \
-p 8080:8080 \
unicorn-store-spring:latest

Open the terminal window with the test command and run it again:

export SVC_URL=localhost:8080
curl --location --request POST $SVC_URL'/unicorns' --header 'Content-Type: application/json' --data-raw '{
"name": "'"Something-$(date +%s)"'",
"age": "20",
"type": "Animal",
"size": "Very big"
}' | jq

Switch back to the Java application terminal window, and stop the application with Ctrl+C.

4. Building the application in the container

Instead of building the Java application on your local machine and then transferring the final binaries to the container, an alternative is to run the build process directly within the container. This approach eliminates the need for your development or build machines to handle the installation of necessary build tools. The image now incorporates all the dependencies needed for building your application.

Copy the new Dockerfile to the current folder

cd ~/environment/unicorn-store-spring
cp dockerfiles/Dockerfile_01_original Dockerfile

Start the build for the container image. While it is building, you can move to the next step and inspect the Dockerfile.

docker buildx build --load -t unicorn-store-spring:latest .

Inspect the Dockerfile. You can now see that we are running the Maven command inside the container:

/unicorn-store-spring/Dockerfile
FROM public.ecr.aws/docker/library/maven:3.9-amazoncorretto-17-al2023 as builder

RUN yum install -y shadow-utils

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

COPY src ./src/
RUN mvn clean package && mv target/store-spring-1.0.0-exec.jar store-spring.jar
RUN rm -rf ~/.m2/repository

RUN groupadd --system spring -g 1000
RUN adduser spring -u 1000 -g 1000

USER 1000:1000

EXPOSE 8080
ENTRYPOINT ["java","-jar","-Dserver.port=8080","/store-spring.jar"]

List the container images:

docker images

Run a container instance:

docker run \
-e SPRING_DATASOURCE_URL=$SPRING_DATASOURCE_URL \
-e SPRING_DATASOURCE_PASSWORD=$SPRING_DATASOURCE_PASSWORD \
-p 8080:8080 \
unicorn-store-spring:latest

Open the terminal window with the test command and run it again:

export SVC_URL=localhost:8080
curl --location --request POST $SVC_URL'/unicorns' --header 'Content-Type: application/json' --data-raw '{
"name": "'"Something-$(date +%s)"'",
"age": "20",
"type": "Animal",
"size": "Very big"
}' | jq

Switch back to the Java application terminal window, and stop the application with Ctrl+C.

Conclusion

This lab explored the process of constructing and running Docker containers. This approach provides an efficient way to manage multi-service applications, which greatly benefits developers by streamlining the process.