Use the wildfly-maven-plugin to create a Docker image of your application
In this article, we will explain how a developer using the Docker image for WildFly can take advantage of the new capabilities of the wildlfy-maven-plugin
and the new WildFly runtime image to build their container image.
We will describe the changes required to move to this new architecture. Once the changes are done, we will provide some examples of the benefits that can be gained from this new architecture.
WildFly, Docker, S2I and what are the synergies?
WildFly provides a Docker image at quay.io/wildfly/wildfly that provides a vanilla WildFly server based on its default standalone configuration.
WildFly also provides Source-to-image (S2I) images to be able to create an application image from the application source using tools targeting OpenShift.
These two types of images, the "vanilla" Docker image and the OpenShift S2I images, have no connection and no synergy. A lot of work has been done in WildFly around provisioning. The S2I images benefitted from this effort but the Docker image did not… until now.
While S2I is a good technology to build application images on OpenShift, we realized that we could achieve a more flexible solution that would benefit other container platforms (Kubernetes, Azure) as well as users running WildFly on premise.
We decided to focus on a Maven-centric approach to provision WildFly so that Maven (and the user) would be in control of the complete runtime (including the user deployment as well as the WildFly runtime). This Maven-centric approach can also benefit the users of the vanilla Docker image for WildFly and help them move to a consolidated architecture.
WildFly Docker image
There are different ways to use the quay.io/wildfly/wildfly image to create an application image (that would contain both WildFly and your deployments) but the simplest one is to add the deployment archive to this base image and let WildFly deploy it when it starts.
As an example, we will start from a simple Java application, the microprofile-config
quickstart.
For the purpose of this exercise, the Java application is a blackbox and we will not look at it. We just want to ensure that we can create an application image that can run it.
Let’s clone the repository and build the application:
git clone https://github.com/wildfly/quickstart.git
cd quickstart/microprofile-config
mvn clean package
Once the Maven build is finished, the deployment archive has been created in target/microprofile-config.war
.
At this point, we can use the WildFly Docker image to create the application image with a simple Dockerfile
:
FROM quay.io/wildfly/wildfly
ADD target/microprofile-config.war /opt/jboss/wildfly/standalone/deployments/
Let’s build a wildfly-app
image from this Dockerfile and run it locally:
docker build -t wildlfy-app .
docker run -p 8080:8080 wildfly-app
That’s all we needed to run WildFly and the application from docker. We can verify that it is working with:
curl http://localhost:8080/microprofile-config/
and it replies MicroProfile Config quickstart deployed successfully. You can find the available operations in the included README file
.
WildFly Provisioning Architecture
Let’s now move to the new architecture for WildFly. Jean-Francois Denise extensively described it in the WildFly S2I new architecture is final! article and focused on the Source-to-Image (S2I) capabilities of the architecture.
In this article, we will see that this architecture also benefits users who are not using S2I and want to keep control of the creation of their application images.
With this new architecture, the provisioning of WildFly (which provides the ability to download and create a distribution of WildFly fit for the application requirements) is handled by the Maven plugin org.wildfly.plugins:wildfly-maven-plugin
.
We can add it to the <build>
section of application’s pom.xml
to provision WildFly when the package
goal is executed by Maven:
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>4.0.0.Beta2</version>
<configuration>
<feature-packs>
<feature-pack>
<location>org.wildfly:wildfly-galleon-pack:26.1.1.Final</location>
</feature-pack>
</feature-packs>
</configuration>
<executions>
<execution>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
If we run again mvn clean package
, the wildfly-maven-plugin
will provision WildFly using its feature pack.
Once maven is finished, there will be a target/server
directory that contains WildFly and the application deployment.
This means that you can directly run WildFly from this directory with the application deployed in it:
./target/server/bin/standalone.sh
...
12:32:00,134 INFO [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0010: Deployed "microprofile-config.war" (runtime-name : "microprofile-config.war")
...
12:32:00,196 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 26.1.1.Final (WildFly Core 18.1.1.Final) started in 8929ms - Started 423 of 623 services (341 services are lazy, passive or on-demand) - Server configuration file in use: standalone.xml
This is the fundamental change with this architecture: the provisioning, capability trimming and customization of WildFly is now controlled by the application’s pom.xml
.
In that sense, the application’s pom.xml
controls the full runtime of the application. You no longer need to install WildFly, create the deployment and deploy it in WildFly. Instead, the WildFly installation and the deployment is done as part of the Maven build process.
You can really see your pom.xml
as the central point of your application which is composed of the WildFly runtime and your deployment archive.
To leverage this change, we have developed a new image quay.io/wildfly/wildfly-runtime-jdk11
that contains everything needed to run WildFly with OpenJDK 11.
If we want to create an application image, we can change the Dockerfile
to use this runtime image and add the target/server
to it:
FROM quay.io/wildfly/wildfly-runtime-jdk11
COPY --chown=jboss:root target/server $JBOSS_HOME
RUN chmod -R ug+rwX $JBOSS_HOME
Let’s build again the wildfly-app
image from this updated Dockerfile
and run it:
docker build -t wildlfy-app .
docker run -p 8080:8080 wildfly-app
We can see that there is no change from a caller perspective and the application can still be queried with:
curl http://localhost:8080/microprofile-config/
Moving from WildFly Docker image to Runtime image
Let’s review what is needed to move from the vanilla Docker image to the new runtime image for WildFly:
-
add the
org.wildfly.plugins:wildfly-maven-plugin
to the application’spom.xml
-
update the
Dockerfile
to use the new runtime image and add thetarget/server
directory
Now that we have moved to the new architecture, what are the benefits of it?
Capability Trimming
WildFly provides capability trimming with layers so that WildFly is provisioned with only the components (mostly Java archives) that are needed to run your application and nothing more. There are two key benefits with capability trimming:
-
It reduces the security risk as you are not subject to security attacks if the affected components are not present in application at all.
-
It reduces the size of the server runtime.
In our example, our microprofile-config
quickstart requires MicroProfile to run. WildFly provides a convenient microprofile-platform
that provisions everything that is needed to run MicroProfile applications. We can trim our runtime to only this layer by updating the wildfly-maven-plugin
:
<configuration>
<feature-packs>
<feature-pack>
<location>org.wildfly:wildfly-galleon-pack:26.1.1.Final</location>
</feature-pack>
</feature-packs>
<layers>
<layer>microprofile-platform</layer>
</layers>
</configuration>
If we package again the application with mvn clean package
, we can notice that the size of the target/server
went from 250M
to 73M
and a lot of jars that were not needed to run the application are no longer present.
Packaging Scripts
The wildfly-maven-plugin
also provides the ability to execute JBoss CLI commands when WildFly is provisioned. This allows you to substantially modify the standalone configuration to better fit the application requirements. It has no impact on the application image as these scripts are only invoked during provisioning.
As a basic example, let’s say we want to support Cross-Origin Resource Sharing (CORS) that requires to add some resources to the undertow
subsystem.
To active CORS in our application, we need to write a CLI script that creates these resources and put them in the application project in the src/main/scripts/cors.cli
:
echo Adding Undertow Filters for CORS
# Access-Control-Allow-Origin
/subsystem=undertow/server=default-server/host=default-host/filter-ref="Access-Control-Allow-Origin":add()
/subsystem=undertow/configuration=filter/response-header="Access-Control-Allow-Origin":add(header-name="Access-Control-Allow-Origin",header-value="${env.CORS_ORIGIN:*}")
# Access-Control-Allow-Methods
/subsystem=undertow/server=default-server/host=default-host/filter-ref="Access-Control-Allow-Methods":add()
/subsystem=undertow/configuration=filter/response-header="Access-Control-Allow-Methods":add(header-name="Access-Control-Allow-Methods",header-value="GET, POST, OPTION, PUT, DELETE, PATCH")
# Access-Control-Allow-Headers
/subsystem=undertow/server=default-server/host=default-host/filter-ref="Access-Control-Allow-Headers":add()
/subsystem=undertow/configuration=filter/response-header="Access-Control-Allow-Headers":add(header-name="Access-Control-Allow-Headers",header-value="accept, authorization, content-type, x-requested-with")
# Access-Control-Allow-Credentials
/subsystem=undertow/server=default-server/host=default-host/filter-ref="Access-Control-Allow-Credentials":add()
/subsystem=undertow/configuration=filter/response-header="Access-Control-Allow-Credentials":add(header-name="Access-Control-Allow-Credentials",header-value="true")
# Access-Control-Max-Age
/subsystem=undertow/server=default-server/host=default-host/filter-ref="Access-Control-Max-Age":add()
/subsystem=undertow/configuration=filter/response-header="Access-Control-Max-Age":add(header-name="Access-Control-Max-Age",header-value="1")
We can then add this script to the wildfly-maven-plugin
by extending its configuration:
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>4.0.0.Beta2</version>
<configuration>
...
<packaging-scripts>
<packaging-script>
<scripts>
<script>${project.build.scriptSourceDirectory}/cors.cli</script>
</scripts>
</packaging-script>
</packaging-scripts>
...
</configuration>
</plugin>
Once the pom.xml
is modified, when you run mvn package
, you can notice the CLI commands that are invoked during the packaging of the application:
mvn clean package
...
[INFO] --- wildfly-maven-plugin:4.0.0.Beta2:package (default) @ microprofile-config ---
[INFO] Provisioning server in /Users/jmesnil/Developer/quickstart/microprofile-config/target/server
...
[standalone@embedded /] echo Adding Undertow Filters for CORS
Adding Undertow Filters for CORS
...
With the ability to run CLI scripts when WildFly is provisioned, you are in total control of the configuration of WildFly.
Feature Packs
WildFly uses feature packs as the building blocks to provision the server.
The most important feature pack is WildFly’s own feature pack: org.wildfly:wildfly-galleon-pack:26.1.1.Final
to control the installation of WildFly itself.
We are also providing additional feature packs to provide additional capabilities to WildFly. It is out of scope of this article to list all of them but let’s discuss two interesting ones:
-
The wildfly-cloud-galleon-pack provides a set of additional features allowing you to configure a WildFly server to work on the cloud. It adapts WildFly to run on orchestration plaftorms in an optimized way. In particular, it automatically routes server logs to the console, it provisions the
health
subsystem to monitor the server health with healthiness probes, etc. -
The wildfly-datasources-galleon-pack provides JDBC drivers and datasources for various databases. If you include this feature pack, you only need to specify the layer corresponding to the databases you want to use (e.g.
postgresql-datasource
). You then only need to specify a few environment variables (e.g. DB URL and credentials) at runtime to connect to the database when WildFly is running.
Conclusion
The wildfly-maven-plugin
is currently at version 4.0.0.Beta2 with a Final release planned for WildFly 27.
It builds on top of the experience we gained from the Bootable Jar and provides a compelling architecture to control the full runtime (WildFly + the application deployments) from the application’s pom.xml
.
The full customization of WildFly (using feature packs, packaging scripts, etc.) is controlled by the developer so that the runtime fits the user’s application.
Creating a container image from this provisioned server is then just a matter of putting it in a runtime image that contains OpenJDK to run the application.
We will continue to deliver the vanilla Docker image for WildFly but we are focusing on the new architecture and the new images to expand the capabilities of WildFly. We are looking forward to our users trying this new approach and validates how it improves their workflow.
We will also start an open conversation to bring additional synergies between the Docker and S2I images for WildFly that could benefit the whole community. In particular, we want to bring new capabilities such as additional architectures (in particular linux/arm64
), newer versions of the JDK (with 17
being the priority), etc. to all our images.
If you see any issue or improvements for this new architecture, please open issues on the WFLY issue tracker.