In this medium, we will be talking about fully dockerizing Quarkus. Luckily for us, Quarkus usually generates the DockerFile for us. We only have to do some modification to establish full dockerization. Afterwards, we will move our application properties to a secure environment file. Finally, we will talk about the remote developing with Quarkus.

The DockerFile

What we have

FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 
ARG JAVA_PACKAGE=java-11-openjdk-headless
ARG RUN_JAVA_VERSION=1.3.8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
# Install java and the run-java script
# Also set up permissions for user `1001`
RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
&& microdnf update \
&& microdnf clean all \
&& mkdir /deployments \
&& chown 1001 /deployments \
&& chmod "g+rwX" /deployments \
&& chown 1001:root /deployments \
&& curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
&& chown 1001 /deployments/run-java.sh \
&& chmod 540 /deployments/run-java.sh \
&& echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=1001 /target/quarkus-app/lib/ /deployments/lib/
COPY --chown=1001 /target/quarkus-app/*.jar /deployments/
COPY --chown=1001 /target/quarkus-app/app/ /deployments/app/
COPY --chown=1001 /target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 1001
ENTRYPOINT [ "/deployments/run-java.sh" ]

Easy enough, this simply informs docker to get the image that is present in the “FROM“ keyword, execute a couple of commands to prepare the environment, and lastly copy the generated jars that were built into the docker container. For more on the docker syntax check this link here. Please note that in order for this docker build to execute correctly, we have to execute the following command prior to building the docker container:

mvn clean package -Dquarkus.package.type=mutable-jar

We may add the argument “-DskipTests=true” to skip running test for time purposes.

What we need to do

FROM maven:3-openjdk-11 AS builder
COPY ./pom.xml /usr/src/app/
RUN mvn -f /usr/src/app/pom.xml -B de.qaware.maven:go-offline-maven-plugin:resolve-dependencies
COPY ./.env /usr/src/app/
COPY src /usr/src/app/src
WORKDIR /usr/src/app
RUN mvn -DskipTests=true -Dquarkus.package.type=mutable-jar -B de.qaware.maven:go-offline-maven-plugin:resolve-dependencies clean package

As you noticed, we will be getting a docker image that has both java and maven. Afterwards, we copy our “pom.xml” and “src” into our container. Finally, we set our working directory to that where everything is copied and build the project.

Again, we may add the argument “-DskipTests=true” to skip running test for time purposes.

We added the argument “dependency:go-offline” for us to keep all downloaded maven repositories and not re-download them each time we want to build our project.

Afterwards we want to copy the generated build into the deployment directory instead of copying the build from our local machine. This can be done by replace the 4 copy commands with the following:

COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/lib/ /deployments/lib/
COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/*.jar /deployments/
COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/app/ /deployments/app/
COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/quarkus/ /deployments/quarkus/

With these updated, we simply stated to get the files from the previous stage and we set the directory they were generated in.

Finally we will get our docker file looking like this:

FROM maven:3-openjdk-11 AS builder
COPY ./pom.xml /usr/src/app/
RUN mvn -f /usr/src/app/pom.xml -B de.qaware.maven:go-offline-maven-plugin:resolve-dependencies
COPY ./.env /usr/src/app/
COPY src /usr/src/app/src
WORKDIR /usr/src/app
RUN mvn -DskipTests=true -Dquarkus.package.type=mutable-jar -B de.qaware.maven:go-offline-maven-plugin:resolve-dependencies clean package
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
ARG JAVA_PACKAGE=java-11-openjdk-headless
ARG RUN_JAVA_VERSION=1.3.8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
# Install java and the run-java script
# Also set up permissions for user `1001`
RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
&& microdnf update \
&& microdnf clean all \
&& mkdir /deployments \
&& chown 1001 /deployments \
&& chmod "g+rwX" /deployments \
&& chown 1001:root /deployments \
&& curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
&& chown 1001 /deployments/run-java.sh \
&& chmod 540 /deployments/run-java.sh \
&& echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/lib/ /deployments/lib/
COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/*.jar /deployments/
COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/app/ /deployments/app/
COPY --chown=1001 --from=builder /usr/src/app/target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 1001
ENTRYPOINT [ "/deployments/run-java.sh" ]

In this way, we guarantee that anyone who wants to use run our project in docker, can build and run it easily without downloading any dependency to their machine.

Property Changes

The Security

LOG_LEVEL=INFO 2HTTP_ACCESS_LOG_ENABLED=true 3LOG_CATEGORY_MONGODB_LEVEL=INFO

For our properties file we will simply reference them as properties file as such:

quarkus.log.level=${LOG_LEVEL}
quarkus.http.access-log.enabled=${HTTP_ACCESS_LOG_ENABLED}

In this manner, for any change we want to do for our properties like increasing the log level, we simple edit the .env file, and restart our container.

I also want to note that we can completely move to the .env file because Quarkus support property and environment mapping which can be seen here. As such, we would not have to create an environment reference inside the properties file.

The Connections

jdbc:mysql://localhost:3308/my_schema

This will unable to connect to the MySQL database because with respect to the container it is not running in it, but on a different container. To fix this, we simply have to replace the hostname with the container name and the port with internal container port and not the external one. As a result, we will have something like this:

jdbc:mysql://${container-name}:${internal-container-port}/my_schema

The Docker Compose

The Composition

quarkus:
container_name: quarkus
image: quarkus
depends_on:
- ${dependency-1}
- ${dependency-2}
build:
context: ./${project-name}
dockerfile: ./src/main/docker/Dockerfile.fast-jar
env_file: ./${project-name}/.env
ports:
- xxxx:8080

The ${project-name} is the directory to our Quarkus project.

The ‘depends_on’ is a handy operation which informs that our container requires other containers for it to run like a mysql and mongodb databases for instance. This will make sure that these services run before our application does.

In the ‘build’, we have to specify the context which is the project directory, and the location of the dockerfile we wish to use.

In the ‘env_file’, we simply map it to the properties file that it will copy and use in the container.

Lastly, we will define the listening ports for our application. By default, the Quarkus project will run our application on port 8080. We may keep it like that because this is running on its own local container. We simply have to map it to an external port of our choosing.

For example, we can have the following docker compose file that contains our application, mongodb, and mysql.

version: '3.8'
services:
mongodb:
container_name: mongodb
image: 'mongo:4.4.3'
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password
ports:
- 27017-27019:27017-27019
volumes:
- ./volumes/mongo:/data/db
quarkus:
container_name: quarkus
image: quarkus
restart: always
volumes:
- $HOME/.m2:/root/.m2
depends_on:
- mongodb
- mysql-integration
build:
context: ./quarkus
dockerfile: ./src/main/docker/Dockerfile.fast-jar
env_file: ./quarkus/.env
ports:
- 5005:5005
- 8080:8080
mysql:
container_name: mysql
image: 'mysql:8.0.23'
restart: always
volumes:
- './volumes/mysql:/var/lib/mysql'
ports:
- 3308:3306
environment:
- MYSQL_ROOT_PASSWORD=servme
command: mysqld --sql_mode=""

The Building

docker-compose build ${container-name}

This will build our container and all of its container dependencies.

The Running

docker-compose up ${container-name}

This will build and run our container in real time. In other words, we can view live logs in our terminal for debugging purpposes. We can simply use ctrl+c to exit, but keep the container. Afterwards, we can run it without viewing the logs using the following command:

docker-compose start ${container-name}

The Stopping

docker-compose stop ${container-name}

The Destruction

docker-compose down

This will destroy our containers and anything inside them. If we want to preserve anything, we can use docker volumes to preserve data and load them into our containers. Please also note that once we destroy and rebuild a container, we will have to redownload all dependencies.

The Remote Development

The Application Properties

%dev.quarkus.package.type=mutable-jar
%dev.quarkus.live-reload.password=changeit

To enable debugging, we have to expose the port 5005 along with the default one provided in our DockerFile.

The .env

QUARKUS_LAUNCH_DEVMODE=true
JAVA_ENABLE_DEBUG=true

The Running

The Remote Connection

mvn quarkus:remote-dev -Dquarkus.live-reload.url=http://${hostname}:${container-external-port} -Dquarkus.package.type=mutable-jar -Dquarkus.live-reload.password=${Reload-password}

In this command, we execute it using the installed maven project. We specify the url to connect to and the external port, and we should specify the password which we set when we build the container.

Get Coding