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: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.