Containers are changing the way we build and deliver software. They are also the essential glue for DevOps and the way to take CI/CD to another level. Put them together and you will have one of the most powerful environments in IT. But can Java EE take advantage of it? Of course! If an application server is an abstraction of Java EE applications, containers are an abstraction of the server, and once you have them built into a standard such as Docker, you have the power to use such tools to manage an application server.
This article is an extract from the book Java EE 8 Cookbook, authored by Elder Moraes.
It belongs to Oracle's official documentation for Java EE 6 and, actually, has been much the same architecture since the times of Sun.
If you pay attention, you will notice that there are different containers: a web container, an EJB container, and an application client container. In this architecture, it means that the applications developed with those APIs will rely on many features and services provided by the container.
When we take the Java EE application server and put it inside a Docker container, we are doing the same thing— it is relying on some of the features and services provided by the Docker environment.
This recipe will show you how to deliver a Java EE application in a container bundle, which is called an appliance.
First, of course, you need the Docker platform installed in your environment. There are plenty of options, so I suggest you check this link and get more details:
And if you are not familiar with Docker commands, I recommend you have a look at this beautiful cheat sheet:
You'll also need to create an account at Docker Hub so you can store your own images. Check it out.
It's free for public images.
To build your Java EE container, you'll first need a Docker image. To build it, you'll need a Dockerfile such as this:
FROM openjdk:8-jdk
ENV GLASSFISH_HOME /usr/local/glassfish
ENV PATH ${GLASSFISH_HOME}/bin:$PATH
ENV GLASSFISH_PKG latest-glassfish.zip
ENV GLASSFISH_URL https://download.oracle.com/glassfish/5.0/nightly/latest-glassfish.zip
RUN mkdir -p ${GLASSFISH_HOME}
WORKDIR ${GLASSFISH_HOME}
RUN set -x
&& curl -fSL ${GLASSFISH_URL} -o ${GLASSFISH_PKG}
&& unzip -o $GLASSFISH_PKG
&& rm -f $GLASSFISH_PKG
&& mv glassfish5/* ${GLASSFISH_HOME}
&& rm -Rf glassfish5
RUN addgroup glassfish_grp
&& adduser --system glassfish
&& usermod -G glassfish_grp glassfish
&& chown -R glassfish:glassfish_grp ${GLASSFISH_HOME}
&& chmod -R 777 ${GLASSFISH_HOME}
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh
USER glassfish
ENTRYPOINT ["/docker-entrypoint.sh"]
EXPOSE 4848 8080 8181
CMD ["asadmin", "start-domain", "-v"]
This image will be our base image from which we will construct other images in this chapter. Now we need to build it:
docker build -t eldermoraes/gf-javaee-jdk8 .
Go ahead and push it to your Docker Registry at Docker Hub:
docker push eldermoraes/gf-javaee-jdk8
Now you can create another image by customizing the previous one, and then put your app on it:
FROM eldermoraes/gf-javaee-jdk8
ENV DEPLOYMENT_DIR ${GLASSFISH_HOME}/glassfish/domains/domain1/autodeploy/
COPY app.war ${DEPLOYMENT_DIR}
In the same folder, we have a Java EE application file (app.war) that will be deployed inside the container. Check the See also section to download all the files.
Once you save your Dockerfile, you can build your image:
docker build -t eldermoraes/gf-javaee-cookbook .
Now you can create the container:
docker run -d --name gf-javaee-cookbook -h gf-javaee-cookbook -p 80:8080 -p 4848:4848 -p 8686:8686 -p 8009:8009 -p 8181:8181 eldermoraes/gf-javaee-cookbook
Wait a few seconds and open this URL in your browser:
http://localhost/app
Let's understand our first Dockerfile:
FROM openjdk:8-jdk
This FROM keyword will ask Docker to pull the openjdk:8-jdk image, but what does it mean?
It means that there's a registry somewhere where your Docker will find prebuilt images. If there's no image registry in your local environment, it will search for it in Docker Hub, the official and public Docker registry in the cloud.
And when you say that you are using a pre-built image, it means that you don't need to build, in our case, the whole Linux container from scratch. There's already a template that you can rely on:
ENV GLASSFISH_HOME /usr/local/glassfish
ENV PATH ${GLASSFISH_HOME}/bin:$PATH
ENV GLASSFISH_PKG latest-glassfish.zip
ENV GLASSFISH_URL https://download.oracle.com/glassfish/5.0/nightly/latest-glassfish.zip
RUN mkdir -p ${GLASSFISH_HOME}
WORKDIR ${GLASSFISH_HOME}
Here are just some environment variables to help with the coding.
RUN set -x && curl -fSL ${GLASSFISH_URL} -o ${GLASSFISH_PKG} && unzip -o $GLASSFISH_PKG && rm -f $GLASSFISH_PKG && mv glassfish5/* ${GLASSFISH_HOME} && rm -Rf glassfish5
The RUN clause in Dockerfiles execute some bash commands inside the container when it has been created. Basically, what is happening here is that GlassFish is being downloaded and then prepared in the container:
RUN addgroup glassfish_grp && adduser --system glassfish && usermod -G glassfish_grp glassfish && chown -R glassfish:glassfish_grp ${GLASSFISH_HOME} && chmod -R 777 ${GLASSFISH_HOME}
For safety, we define the user that will hold the permissions for GlassFish files and processes:
COPY docker-entrypoint.sh / RUN chmod +x /docker-entrypoint.sh
Here we are including a bash script inside the container to perform some GlassFish administrative tasks:
#!/bin/bash
if [[ -z $ADMIN_PASSWORD ]]; then
ADMIN_PASSWORD=$(date| md5sum | fold -w 8 | head -n 1)
echo "##########GENERATED ADMIN PASSWORD: $ADMIN_PASSWORD
##########"
fi
echo "AS_ADMIN_PASSWORD=" > /tmp/glassfishpwd
echo "AS_ADMIN_NEWPASSWORD=${ADMIN_PASSWORD}" >> /tmp/glassfishpwd
asadmin --user=admin --passwordfile=/tmp/glassfishpwd change-admin-password --domain_name domain1
asadmin start-domain
echo "AS_ADMIN_PASSWORD=${ADMIN_PASSWORD}" > /tmp/glassfishpwd
asadmin --user=admin --passwordfile=/tmp/glassfishpwd enable-secure-admin
asadmin --user=admin stop-domain
rm /tmp/glassfishpwd
exec "$@"
After copying the bash file into the container, we go to the final block:
USER glassfish
ENTRYPOINT ["/docker-entrypoint.sh"]
EXPOSE 4848 8080 8181
CMD ["asadmin", "start-domain", "-v"]
The USER clause defines the user that will be used from this point in the file. It's great because from there, all the tasks will be done by the glassfish user.
The ENTRYPOINT clause will execute the docker-entrypoint.sh script.
The EXPOSE clause will define the ports that will be available for containers that use this image.
And finally, the CMD clause will call the GlassFish script that will initialize the container.
Now let's understand our second Dockerfile:
FROM eldermoraes/gf-javaee-jdk8
We need to take into account the same considerations about the prebuilt image, but now the image was made by you. Congratulations!
ENV DEPLOYMENT_DIR ${GLASSFISH_HOME}/glassfish/domains/domain1/autodeploy/
Here, we are building an environment variable to help with the deployment. It's done in the same way as for Linux systems:
COPY app.war ${DEPLOYMENT_DIR}
This COPY command will literally copy the app.war file to the folder defined in the DEPLOYMENT_DIR environment variable.
From here, you are ready to build an image and create a container. The image builder is self-explanatory:
docker build -t eldermoraes/gf-javaee-cookbook .
Let's check the docker run command:
docker run -d --name gf-javaee-cookbook -h gf-javaee-cookbook -p 80:8080 -p 4848:4848 -p 8686:8686 -p 8009:8009 -p 8181:8181 eldermoraes/gf-javaee-cookbook
If we break it down, this is what the various elements of the command mean:
So now you've successfully built a container for your Java EE application, in Docker. If you found this tutorial helpful and would like to learn more, head over to the Packt store and get the book Java EE 8 Cookbook, authored by Elder Moraes.
Oracle announces a new pricing structure for Java
Design a RESTful web API with Java [Tutorial]
How to convert Java code into Kotlin