Deploy a Meecrowave (or Maven based) application in Docker easily
Docker is more and more used in the industry for good (or not) reasons. It means you need to be able to write a Dockerfile more and more often to be able to deploy your application. Most common cases are either to prepare the production deployment and get scalability thanks to the production environment or to share products/softwares between teams.
This is not a huge task by itself because its role is to "grab" the application, "deploy" it and finally "start" it.
Depending your environment, all these steps are different. However some tips can be interesting and used for most of (Java) applications.
To illustrate it and show that writing a Dockerfile is not that hard under some circumstances, we will assume you want to deploy a Meecrowave application.
The entry point of our application will be a Meecrowave bundle (mvn meecrowave:bundle), it is zip created at build time and easily deployable on nexus (in any release if you don't change the defaults).
Note: most of applications can be aligned on such hypothesis. All JavaEE applications can be deployed in a TomEE bundled as a zip with TomEE maven plugin, Spring Boot applications can be zipped as well etc...
Concretely, we will assume we deploy our application - release or snapshot - on a Nexus, which is a quite common case. If you use another artifact repository, it will be easy to adapt the commands.
The trick here is to know that you can download an artifact from it coordinates (group, artifact, version, type, optionally classifier), even in snapshot and if the nexus uses timestamp for snapshots. Instead of using a direct URL you use a webservice url: /service/local/artifact/maven/content.
Then our docker file is pretty easy:
- define in the FROM the base image. It can be anything with java
- (optionally, depends previous choice) ensure you have gpg if the artifacts are signed and you can use wget on HTTPS (some images don't have ssl installed by default)
- setup your working directory
- download the artifact and its signature (if signed)
- verify the signature and that the downloaded artifact was not corrupted (if signed only)
- unzip the bundle
- copy the bundle files in the right folder
- remove the temporary files (the zip)
- (optionally) write some data about the deployment like the date and coordinates
- (optionally) copy some specific configuration files like the logging configuration
- ensure scripts are executable
- define which port is used by default
- define the command to launch the application
For Meecrowave it can look like this:
FROM openjdk:8-jre-alpine
MAINTAINER xxx@yyy.com
ENV LC_ALL en_US.UTF-8
RUN set -xe && \
apk add --no-cache gnupg ca-certificates openssl && \
update-ca-certificates && \
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys XXXXXXX
ENV MEECROWAVE_BASE /opt/application
RUN mkdir -p $MEECROWAVE_BASE
WORKDIR $MEECROWAVE_BASE
ARG SERVER_VERSION
RUN set -ex && \
[ -n "$SERVER_VERSION" ] || { echo "[ERROR] No version defined, set environment variable SERVER_VERSION when launching the image"; exit 1; } && \
export NEXUS_BASE=${NEXUS_BASE:-https://oss.sonatype.org} && \
export GROUP_ID=${GROUP_ID:-org.company} && \
export ARTIFACT_ID=${ARTIFACT_ID:-myapplication} && \
export REPOSITORY=$([[ "${SERVER_VERSION%-SNAPSHOT}" != "$SERVER_VERSION" ]] && echo 'snapshots' || echo 'releases') && \
export DOWNLOAD_URL="$NEXUS_BASE/service/local/artifact/maven/content?r=$REPOSITORY&g=$GROUP_ID&a=$ARTIFACT_ID&v=$SERVER_VERSION&e=zip" && \
echo "Using artifact $GROUP_ID:$ARTIFACT_ID:zip:$SERVER_VERSION" && \
wget $DOWNLOAD_URL.asc -O myapplication.zip.asc && \
wget $DOWNLOAD_URL -O myapplication.zip && \
gpg --batch --verify myapplication.zip.asc myapplication.zip && \
unzip myapplication.zip && \
mv myapplication/* $MEECROWAVE_BASE && \
rm -Rf myapplication && \
rm myapplication.zip* && \
echo "$GROUP_ID:$ARTIFACT_ID:zip:$SERVER_VERSION" > conf/build.gav && \
date > conf/build.date
COPY conf/* $MEECROWAVE_BASE/conf/
COPY bin/* $MEECROWAVE_BASE/bin/
RUN chmod +x bin/*.sh
EXPOSE 8080
CMD [ "./bin/meecrowave.sh", "run" ]
Here we start from OpenJDK image bringing back a JRE 8 (not a JDK for security reasons). Then we set the maintainer of the image and default the locale to the english one to avoid surprises. The next RUN installed GPG and SSL support and retrieve the GPG key we will use to verify the binary signing. Then we setup a working directory which will be our installation directory for the application.
The next block is where most of the logic is. We define a ARG variable to let inject the version at build time (we will see how soon). Then the script verifies this variable is set, setup some variables which can optionally be overriden like the nexus base URL (default to the OSS sonatype one), the groupId, artifactId of the artifact to deploy. It deduces from the version which repository to use (releases or snapshots) and finally computes the URL to get the artifact from.
Note that at this point you can also add some security token setup if your nexus is not public.
The end of the script gets the artifact and its signature, verifies it, and does the setup of the application in the working directory.
The COPY directives allow to synchronize local folders with the image folders to copy configuration and optionally some scripts (like setenv.sh).
The last two lines defines the port which will be exposed and the command to launch (which starts the server).
With this kind of script you can already deploy your application once it is available on Nexus. However, before seeing how to use it, I'd like to share a small trick about the logging. Most of the time the logs will be headless (logging in a file or to an aggregator) but no console output. This is a bit an issue during the development if you rely on docker images since you want to see the logs in the output without having to connect to the image most of the time. To solve it you can add a console appender and switch it off by default. Here is an example:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{HH:mm:ss.SSS}][%highlight{%-5level}][%15.15t][%30.30logger] %msg%n"/>
</Console>
<RollingFile name="File"
fileName="${base}/application.log"
filePattern="${base}/application-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="[%d{HH:mm:ss.SSS}][%highlight{%-5level}][%15.15t][%30.30logger] %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="File" />
<AppenderRef ref="Console" level="${env:CONSOLE_LOG_LEVEL:-OFF}" />
</Root>
</Loggers>
</Configuration>
Here all the trick is in the level of the console appender. By default it will be OFF but if you set CONSOLE_LOG_LEVEL to INFO launching the image, then you will get the logs into the console as well.
In docker, it can be beneficial to define some JVM options. To hardcode a few we can define a bin/setenv.sh with this content:
#! /bin/sh
export MEECROWAVE_OPTS="$MEECROWAVE_OPTS -XX:+UnlockExperimentalVMOptions"
export MEECROWAVE_OPTS="$MEECROWAVE_OPTS -XX:+UseCGroupMemoryLimitForHeap"
These options are mainly to correctly setup the memory on the JVM in a docker environment - JVM defaults are not that great otherwise.
Now we are ready to build our image. With previous script we must pass our artifact version at that time to not fail:
docker build \
--build-arg SERVER_VERSION=1.0.0 \
--tag myapplication:1.0.0 \
.
And to run the application once built:
docker run \
-p 8080:8080 \
myapplication:1.0.0
If you want the logs in the console you can launch it this way - adding the dedicated variable:
docker run \
-p 8080:8080 \
-e CONSOLE_LOG_LEVEL=INFO \
myapplication:1.0.0
And if you want to pass additional options to the Meecrowave JVM, just customize the MEECROWAVE_OPTS variable:
docker run \
-p 8080:8080 \
-e MEECROWAVE_OPTS="-Dmy.conf=foo" \
myapplication:1.0.0
Here we are, we have a docker image ready to be used either in development or in production with our application :).
From the same author:
In the same category: