Chaining Docker Images and execute in order

3.4k Views Asked by At

I am extending the APIMan / Wildfly Docker image with my own image which will do two things:

1) Drop my .war file application into the Wildfly standalone/deployments directory

2) Execute a series of cURL commands that would query the Wildfly server in order to configure APIMan.

Initially, I tried creating two Docker images (the first to drop in the .war file and the second to execute the cURL commands), however I incorrectly assumed that the CMD instruction in the innermost image would be executed first and CMD's would be executed outward.

For example:

ImageA:
FROM jboss/apiman-wildfly:1.1.6.Final
RUN /opt/jboss/wildfly/bin/add-user.sh admin admin --silent
COPY /RatedRestfulServer/target/RatedRestfulServer-1.0-SNAPSHOT.war /opt/jboss/wildfly/standalone/deployments/

CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0", "-c", "standalone-apiman.xml"]

And

ImageB:
FROM ImageA
COPY /configure.sh /opt/jboss/wildfly/

CMD ["/opt/jboss/wildfly/configure.sh"]

I had initially assumed that during runtime Wildfly / APIMAN would be started first (per the ImageA CMD instruction) and then my custom script would be run (per ImageB CMD instruction). I'm assuming that's incorrect because throughout the entire hierarchy, only 1 CMD instruction is executed (the last one in the outermost Dockerfile within the chain)?

So, I then attempted to merge everything into 1 Dockerfile which would (in the build process) startup Wildfly / APIMAN, run the cURL commands, shutdown the wildfly server and then the CMD command would start it back up during runtime and Wildfly / APIMan would be configured. This, however, does not work because when I start Wildfly (as part of the build), it controls the console and waits for log messages to display, thus the build never completes. If I append an '&' at the end of the RUN command, it does not run (Dockerfile : RUN results in a No op).

Here is my Dockerfile for this attempt:

FROM jboss/apiman-wildfly:1.1.6.Final
RUN /opt/jboss/wildfly/bin/add-user.sh admin admin --silent
COPY /RatedRestfulServer/target/RatedRestfulServer-1.0-SNAPSHOT.war /opt/jboss/wildfly/standalone/deployments/
COPY /configure.sh /opt/jboss/wildfly/
RUN /opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0 -c standalone-apiman.xml
RUN /opt/jboss/wildfly/configure.sh
RUN /opt/jboss/wildfly/bin/jboss-cli.sh --connect controller=127.0.0.1 command=:shutdown

CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0", "-c", "standalone-apiman.xml"]

Are there any solutions to this? I'm trying to have my "configure.sh" script run AFTER Wildfly / APIMan are started up. It wouldn't matter to me whether this is done during the build process or at run time, however I don't see any way to do it during the build process because Wildfly doesn't have a daemon mode.

3

There are 3 best solutions below

0
On BEST ANSWER

The original premise behind my problem (though it was not explicitly stated in the original post) was to configure APIMan within the image and without any intervention outside of the image.

It's a bit of a hack, but I was able to solve this by creating 3 scripts. One for starting up Wildfly, one for running the configuration script and a third to execute them both. Hopefully, this saves some other poor soul from spending a day figuring all of this out.

Because of the nature of the Dockerfile only allowing 1 execution call at runtime, that call needed to be to a custom script.

Below are the files with comments.

Dockerfile

FROM jboss/apiman-wildfly:1.1.6.Final
RUN /opt/jboss/wildfly/bin/add-user.sh admin admin --silent
COPY /RatedRestfulServer/target/RatedRestfulServer-1.0-SNAPSHOT.war /opt/jboss/wildfly/standalone/deployments/
COPY /configure.sh /opt/jboss/wildfly/
COPY /execute.sh /opt/jboss/wildfly/
COPY /runWF.sh /opt/jboss/wildfly/

CMD ["/opt/jboss/wildfly/execute.sh"]

Note, all 3 scripts are built into the image. The execute.sh script is executed at runtime (instantiation) not build-time.

execute.sh

#!/bin/sh

/opt/jboss/wildfly/configure.sh &
/opt/jboss/wildfly/runWF.sh

Note, the configure.sh script is sent to the background so that we can move on to the runWF.sh script while configure.sh is still running)

configure.sh

#!/bin/sh

done=""
while [ "$done" != "200" ]
do
        done=$(curl --write-out %{http_code} --silent --output /dev/null -u username:password -X GET -H "Accept: application/json" http://127.0.0.1:8080/apiman/system/status)
        sleep 3
done

# configuration curl commands
curl ...
curl ...

The above configure.sh script runs in a loop querying the wildfly / apiman server via curl every 3 seconds checking its status. Once it gets back an HTTP status code of 200 (representing an "up and running" state), it exits the loop and moves freely on to the configuration. Note, this should probably be made a bit 'safer' by providing another way to exit the loop (e.g. after a certain number of query's, etc.). I imagine this would give a production developer heart palpitations and I wouldn't suggest deploying it in production, however it works for the time being.

runWF.sh

#!/bin/sh

/opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0 -c standalone-apiman.xml

This script simply starts up the server. The parameters bind various modules to 0.0.0.0 and directs wildfly to use the apiman standalone xml file for configuration.

On my machine, it takes wildfly + apiman (with my custom war file) about 10-15 seconds (depending on which computer I run it on) to fully load, but once it does, the configure script will be able to query it successfully and then move on with the configuration curl commands. Meanwhile, wildfly still controls the console because it was started up last and you can monitor the activity and terminate the process with ctrl-c.

0
On

only 1 CMD instruction is executed (the last one in the outermost Dockerfile within the chain)?

Yes this is correct, and keep in mind that CMD is not run at build time but at instantiation time. In essence what you are doing in your second Dockerfile's CMD is overriding the first one when you instantiated the container from ImageB

If you are doing some sort of Rest API or cli or cURL to connect to your Wildfly server I suggest you do that configuration after the container's instantiation, not at the after the container's build. This way:

CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0", "-c", "standalone-apiman.xml"]`

is always your last command. If you need some extra files or changes to configuration files you can put them in the Dockerfile so that they get copied at build time before CMD gets called at instantiation.

So in summary:

1) Build your Docker container with this Dockerfile (docker build):

FROM jboss/apiman-wildfly:1.1.6.Final
RUN /opt/jboss/wildfly/bin/add-user.sh admin admin --silent
COPY /RatedRestfulServer/target/RatedRestfulServer-1.0-SNAPSHOT.war /opt/jboss/wildfly/standalone/deployments/
COPY /configure.sh /opt/jboss/wildfly/
CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0", "-c", "standalone-apiman.xml"]

2) Run (instantiate your container from your newly created image)

docker run <image-id>

3) Run the following from a container or from your host that has Wildfly configured the same way. This assuming you are using some Rest API to configure things (i.e using cURL):

/opt/jboss/wildfly/configure.sh

You can instantiate a second container to run this command with something like this:

docker run -ti <image-id> /bin/bash
1
On

Build one image:

FROM jboss/apiman-wildfly:1.1.6.Final
RUN /opt/jboss/wildfly/bin/add-user.sh admin admin --silent
COPY /RatedRestfulServer/target/RatedRestfulServer-1.0-SNAPSHOT.war /opt/jboss/wildfly/standalone/deployments/
COPY /configure.sh /opt/jboss/wildfly/
CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0", "-c", "standalone-apiman.xml"]

The start it up. After startup is complete, use the docker exec command to launch your configure script within the running container.

docker run -d --name wildfly <image name>
docker exec wildfly /opt/jboss/wildfly/configure.sh