#Docker: Create a custom #Glassfish based enterprise application docker image

By | March 6, 2017


In the following post I am going to describe how to create a custom docker image that contains the following:
– Glassfish Application Server
– Java JDK 1.8
– A custom deployed EJB 3.1 Application

In connection with this a Postgres docker image from which we start a container with a certain database name and user to be used by the application docker image.

STEP 1: Pull from docker.io registry the Glassfish and Postgres images

Note that we already have a local registry.

# docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
docker.io/postgres                latest              f91e27f33f26        4 weeks ago         263.8 MB
docker.io/registry                2                   541a6732eadb        4 weeks ago         33.27 MB
docker.io/mtuanp/glassfish        latest              46d7536ed8af        7 months ago        700 MB

STEP 2: Start new container from postgres image

Specify the following environment variables to the postgres image:
– POSTGRES_USER= a user name to be created when the postgres container is initialized
– POSTGRES_PASSWORD= a user name password to be created when the postgres container is initialized
– POSTGRES_DB= a database to be created when the postgres container is initialized and associated with the above user

This way we can create the custom parameters the EJB3 application expects to exist. The above parameters must be specified in the data source definition in the glassfish container.

docker run -e POSTGRES_USER=myDB -e POSTGRES_PASSWORD=myPassword -e POSTGRES_DB=myDB --name postgres docker.io/postgres

STEP 3: Start a new container from glassfish image

docker run --link postgres:postgres -p 7001:7001 f91e27f33f26

Note: In a docker run command we can either use the alias of the image or the Image ID as the last parameter of the command. Note also that we link the container with the postgres container and we give it the alias postgres. As a result we will be able to access from this container the db with the server name postgres.

List the containers that are started in docker.

# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                                    NAMES
2a5eb73b4c45        735c23377d5d        "/bin/sh -c 'asadmin "   9 days ago          Up 2 hours          4848/tcp, 8080/tcp, 8181/tcp, 9009/tcp,>7001/tcp,>7031/tcp   demo
6a4c538e09eb        f91e27f33f26        "/docker-entrypoint.s"   2 weeks ago         Up 23 hours>5432/tcp                                                                   postgres
48c832c6333c        541a6732eadb        "/entrypoint.sh /etc/"   2 weeks ago         Up 6 days >5000/tcp     


STEP 4: Customize the glassfish based application image with our own JAVA JDK.

The default image we use as a base point has a JAVA 1.7 JRE installed. We need to upgrade it to JDK 1.8 for our EJB 3.1 application.
The tricky part will start now. We will copy inside the container file-system the new JDK.
First try to identify under which devicemapper device the container file-system is mounted.

# df -h
Filesystem              Size  Used Avail Use% Mounted on
devtmpfs                7.8G     0  7.8G   0% /dev
tmpfs                   7.8G   11M  7.8G   1% /dev/shm
tmpfs                   7.8G  2.3M  7.8G   1% /run
tmpfs                   7.8G     0  7.8G   0% /sys/fs/cgroup
/dev/mapper/vg-lv_root   50G   30G   18G  63% /
tmpfs                   7.8G  306M  7.5G   4% /tmp
/dev/sda6               477M  156M  292M  35% /boot
/dev/sda4               200M  5.4M  195M   3% /boot/efi
/dev/mapper/vg-lv_home  411G  372G   19G  96% /home
tmpfs                   1.6G   24K  1.6G   1% /run/user/42
tmpfs                   1.6G   80K  1.6G   1% /run/user/1000
/dev/sdb1               165G  153G  4.0G  98% /ssd
/dev/mapper/truecrypt1   99G   78G   16G  84% /media/truecrypt1
tmpfs                   1.6G     0  1.6G   0% /run/user/0
/dev/mapper/truecrypt2  591G  543G   18G  97% /media/truecrypt2
/dev/dm-6                10G   66M   10G   1% /home/docker/devicemapper/mnt/317b5cfb884027e4a4087b5bc92f5792169df1c13a94e9da66ad38938fe1d457
shm                      64M     0   64M   0% /home/docker/containers/48c832c6333cda0b8b1d6e7200a13d28ef941f6907505f19fed68e951713034e/shm
/dev/dm-7                10G  314M  9.7G   4% /home/docker/devicemapper/mnt/e259eb0e289c565bd8bcefee87df6a7e874aeab7d7cedb4d9658d7201b4f50e7
shm                      64M  4.0K   64M   1% /home/docker/containers/6a4c538e09eb4245c71be3d056366f253c1a6e96b511b67e6dcdaa603d9e8876/shm
/dev/dm-8                10G  1.2G  8.9G  12% /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764
shm                      64M     0   64M   0% /home/docker/containers/2a5eb73b4c4525440d69d92cb00c77672a7f1b4b150fcb08e2fb38271c66a02f/shm

Our application container id is 2a5eb73b4c45 and as we can see there is a devicemapper that has that prefix

/dev/dm-8                10G  1.2G  8.9G  12% /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764

Copy the new Java JDK in the container filesystem:

cp -R /usr/java/jdk1.8.0_45   /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/usr/java/jdk1.8.0_45

Remove the old Java from the container filesystem:

rm -fR /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/usr/java/jdk1.7

Note that this change is only on this container. In case we start a new container from the original image all is lost. In case we stop and restart this same container the change is still there. If we need to interrupt the config at any point make sure to restart container 2a5eb73b4c45 to continue.

STEP 5: Customize the glassfish based application image with our own glassfish 3.1.2

I already have a glassfish domain under which I deployed my EJB 3.1 application. I am not going to insist on how to setup the application specific configurations and how to deploy the binary. In short the application has a JMS connection factory, several JMS queues , a data source and some other glassfish application specific settings for sign-on and security.

There are several steps to set all in order.

STEP 5.1 Copy in the container my own glassfish
I do not like the glassfish application server deployed in the default image. I need Glassfish 3.1.2 with mq support.

Remove the old glassfish from the container/image filesystem:

rm -fR /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/opt/glassfish3

Copy my own glassfish 3.1.2 in the container filesystem:

cp -R /opt/glassfish3   /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/opt/glassfish3

STEP 5.2 Activate secure admin to the container

I want to be able to connect only on https port to the glassfish admin console.
Copy a password.txt file under glassfish3/glassfish in the container filesystem.
Content is like the following:

## Required for Glassfish V1
## Required for Glassfish V1

Then execute the following command to execute the config on the running container

docker exec 2a5eb73b4c45 bin/asadmin --host --port 7001 --passwordfile=glassfish/password.txt enable-secure-admin

After the above command is executed and we stop aqnd start the container we can access the glassfish admin console with:


STEP 5.3 Change the data source to point to the postgres container
Copy first the postgres JDBC driver postgresql-9.4.1211.jar under opt/glassfish3/glassfish/lib in the container filesystem.

Change the JDBC Connection Pool defined to be used by our application to use:

Datasource Classname: org.postgresql.ds.PGConnectionPoolDataSource 

Then under Additional Properties:


serverName    postgres
password      ***
user          myDB
databaseName  myDB
portNumber    5432

Note we refer to the postgres alias we gave to the database container when we started our application container.
Note that we assumed that also the ear binary of the application was custom build to support postgres also.

STEP 5.4 Finalize the configuration and clean-up

At this point we have to do all the other configuration changes in the glassfish admin console.
Execute also a basic clean-up so we can create a new image from this running container.
– delete glassfish logs
– delete /tmp
– delete imq logs
– delete imq lock: /rootfs/opt/glassfish3/glassfish/domains/demo/imq/instances/imqbroker/lock

STEP 6 Create a new image with all the changes

Now this are some very counter intuitive steps one must perform. Due to the layered way docker framework is constructed all the changes we have done in the previous steps are visible only in the container layer.
The devicemapper mount is the container/image file-system. It means that any change to it will be carried out if a new container will be instantiated from the associated image. This also means that some changes from a running container will not be visible there. To make the changes permanent we have to save the changes from the running container and add them to a new image.

STEP 6.1 Copy files from container to host
Using docker cp we will copy the modified files we want to put in the new image to the host. This includes the entire /opt/glassfish3 directory. We are forced to do this because all the changes made from GUI of the glassfish admin console are stored only as file diffs in the container layer.

mkdir container-bkp
docker cp 2a5eb73b4c45:/opt container-bkp 

STEP 6.2 Copy files from host to the container/image filesystem
We copy the files we saved from the container back to the container/image devicemapper

cp -R container-bkp /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/opt/

STEP 6.3 Take a snapshot of current running state of the application container

# docker commit -p 2a5eb73b4c45

With the above command we have first paused a running container with -p option, made a commit to save the entire snapshot as a docker image with a new name.

As an alternative if we had to create several layers during the creation of our image, we can flatten the image by taking a snapshot of the container and then importing it.

#docker export 2a5eb73b4c45 | docker import -

STEP 6.4 Save on disk the new image

The following command will actually save the devicemapper content.

docker save -o demo_app.v3.tar demo_app.v3
gzip demo_app.v3.tar

STEP 6.5 Push the new image to the local private registry

docker push 

STEP 7 Test the new image on a remote site

Copy to remote site and load the image

docker load -i demo_app.v3.tar.gz

Then to start the new environment:

docker pull docker.io/postgres
docker run -e POSTGRES_USER=myDB -e POSTGRES_PASSWORD=myPassword -e POSTGRES_DB=myDB --name postgres docker.io/postgres
docker run --link postgres:postgres -p 7031:7031 image_id

Where image_id is the image id of the loaded image.

STEP 8 Monitor the new application

To monitor the new application start a tail on the domain logs in the container.

docker exec 2a5eb73b4c45 tail -F glassfish/domains/demo/logs/server.log

Execute an interactive bash to have access to more commands, like top or ps.

docker exec -i -t 2a5eb73b4c45 /bin/bash


One thought on “#Docker: Create a custom #Glassfish based enterprise application docker image

  1. George Voina

    Using the above method allows a very easy migration of already available test environments from a development machine. Migrating the test environment to docker images allows the QA department to start in no time work without having to do configurations of a test environment. Also with every new release of the application we just have to release a new application image for the QA to continue without interruption.


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.