Thursday, June 24, 2021

Set up WildFly, PostgreSQL, and Maven with Docker Compose

In many of my previous posts I have resorted to manual installation and configuration of software in my own computer. This was the case of WildFly and MySQL, which cost me more than my fair share of headaches. Manually installing software often runs into troubles when students try to replicate the same setup. In many cases the installation cannot complete  with success on their laptops for some obscure reason. Furthermore, the process of installing and configuring is usually cumbersome and not prone to generalization, as one goes through multiple menus taking different options, downloading drivers and so on.

I changed my mind about this approach some time after deciding to embrace Visual Studio Code (VSC) as my companion text editor (at least for everything related to code, be it LaTeX, Python, or Java). I would say that it still performs clearly below a full-blown IDE, like Eclipse, but the familiarity of the setup is also an important advantage, as I switch from one language to the next. It's a kind of trade-off here. At some point I realized that VSC would let me develop on standard Docker containers, i.e., I could configure an environment inside VSC that I could entirely replicate on another computer with nothing but a couple of configuration lines. This is a remarkable achievement that motivates this post.

Here, I'm presenting a docker compose set up with three containers:
  • The PostgreSQL database.
  • The WildFly application server.
  • And a command line with maven. I could discard one of the containers somehow, e.g., by mounting a volume with WildFly on this last container, but since I'm already needing two containers, adding a third ones comes nearly for free.
For this example to work you need to have Docker installed. I tried this on MacOS and assume that this will work straightforwardly on Linux. Please let me know of your Windows experience.

The docker-compose.yaml

The general idea is to define a yaml compose file, docker-compose.yaml:

version: '3.4'

services:
database:
image: postgres:9
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres # The PostgreSQL user (useful to connect to the database)
POSTGRES_PASSWORD: My01pass # The PostgreSQL password (useful to connect to the database)
POSTGRES_DB: school # The PostgreSQL default database (automatically created at first launch)
wildfly:
build:
context: .
dockerfile: Dockerfile
links:
- database
ports:
- 8080:8080
- 9990:9990
command-line:
image: maven:3-openjdk-17
command: tail -f /dev/null
links:
- database
- wildfly
volumes:
- ..:/workspace

We can see the three services I mentioned. Two of them have the tag "image", which contains a direct reference to the Docker hub container to download (postgres and maven). To prevent the maven container from finishing, we run an interminable script to read from /dev/null. The wildfly service is different, as it builds on a Dockerfile available in the same directory as this yaml file (the "." part). More about this Dockerfile ahead.

One can also see that each one of the containers links to the ones before: wildfly "sees" the database container and may access it using the network name "database", while the command-line service (maven) can see the previous two. I also added port mappings for the database and wildfly, as we want to access the database, and mainly, the WildFly server from our own browser on the very same ports 8080 and 9990.

In this example, I'm setting the environment of the database directly, which is not perfect, because this configuration file provides direct knowledge of the password. It might be better to use environment variables or refer to an external file with the environment configuration. Finally, notice the volume we are mounting in the command-line service: the ".." directory will show up inside the container as "/workspace"; whatever you write starting in the previous directory of your computer will show up in the /workspace and vice-versa. The main idea is that you can compile inside the /workspace in the container, using maven, while keeping everything, including the results outside the container. The container may go, but the results will stay.


The Dockerfile

FROM jboss/wildfly:24.0.0.Final
RUN /opt/jboss/wildfly/bin/add-user.sh admin admin#7rules --silent
ADD --chmod=0755 wildfly-init-config.sh /opt/jboss/wildfly/bin
CMD ["/opt/jboss/wildfly/bin/wildfly-init-config.sh"]

We need this file because the base wildfly container doesn't do everything we need. We must set up an administrator account and a data source. The administrator part is easy, but the data source not so much. The challenge is that we must first start WildFly, make sure that it is running and---only then---install the data source. Hence, we may resort to a shell script called "wildfly-init-config.sh" for that purpose.

The wildfly-init-config.sh script

#! /bin/sh

#start wildfly
/opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0 &

#install the driver and the data source
#the challenge is to have the server ready---it may take a while
RC=1
count=0
while [ $RC -ne 0 ] && [ $count -lt 5 ]
do
sleep 5
/opt/jboss/wildfly/bin/jboss-cli.sh "connect","deploy --force --url=https://jdbc.postgresql.org/download/postgresql-42.2.22.jar","data-source add --name=PostgreDS --driver-name=postgresql-42.2.22.jar --driver-class=org.postgresql.Driver --jndi-name=java:/PostgresDS --connection-url=jdbc:postgresql://database:5432/school --user-name=postgres --password=My01pass"
RC=$?
let count++
done

#keep running forever
tail -f /dev/null

This script starts WildFly in background making sure that the sockets bind to address 0.0.0.0, to enable connection from our own machine. Then, it tries to deploy the PostgreSQL JDBC driver and install a data source, at most 5 times, until it succeeds, because the connect may fail a couple of times while WildFly starts. This gives 25 seconds for WildFly to start. In the end we just hang the script to prevent the container from finishing using no CPU in the process.

Adding everything up

We now need to run a couple of commands on the command line. One to build the images and run the containers:

docker-compose up --build


This may take a while depending on your network speed. Once this finishes, we may try the addresses localhost:8080 and localhost:9990 on the browser, to get the following results, starting from the 8080 port:
The driver:

And the data source that passes the connection test to the PostgreSQL database:


The next command is optional and allows us to enter the container that has maven, such that we can run maven itself. In the end, this was what we were looking for, a fancy way of running maven with minimal installation of software on our own operating system. To do this, we start by looking for the identifiers of the running containers:

docker ps


Then, we look for the identifier of the maven container. In my case it is: 4a229ca55f65. You need to replace the identifier of the container by the identifier of your specific container.

docker exec -it 4a229ca55f65 /bin/sh


You can now run maven having a view of your parent directory ("..") inside this container under the name "workspace":

sh-4.4# ls workspace/

EJB-client  EJB-server JMS  JPA-standalone  KafkaStreams  LICENSE  README.md  application  kafka  local-maven-repo


Another very important feature is to look at the logs, specially of WildFly, as these often give precious indications of what is wrong. For example, a shortcoming of this setup concerns Java versions, as the maven container is ready for Java 17, but the WildFly class loader doesn't seem to accept anything above 11. While Maven was giving me some ambiguous error message, the logs immediately showed the problem. To look at the logs we may use the following command:

docker logs d7882574eb38


being d7882574eb38 the WildFly Docker container.

Note that you must take the versions problem into consideration when compiling the source code. As soon as I changed the Java compiled version to 11 in Maven everything ran smoothly. As I played around a little bit with this version of WildFly, it seemed to me that it is highly unstable and prone to crash. You may, therefore, look for a better version.

Optional Final Step: Visual Studio Code

Instead of doing everything on the command line, you may use this compose set up in IDEs or text editors like VSC. In the case of VSC, this will make part of VSC work inside the container, the VSC server, which is thus able to reach the workspace directory and the tools available in the command line container, namely maven. For this to work, include the following file, named "devcontainer.json" close to the previous files, all inside the same directory named ".devcontainer":

{
"name": "maven+wildfly+postgresql",
"dockerComposeFile": "docker-compose.yml",
"workspaceFolder": "/workspace",
"service": "command-line",
"extensions": [
"dotjoshjohnson.xml",
"vscjava.vscode-maven",
"vscjava.vscode-java-pack",
"redhat.fabric8-analytics"
]
}

Notice the references to the docker-compose.yaml file and to the service. I have also installed a few extensions. Once you issue a "reopen in container", the entire VSC environment will be ready for maven+wildfly+postgresql. You can see the result here (notice the orange rectangle in the lower left corner):


Should you open a terminal, it will be inside the maven container.