In previous posts, we discussed how to deploy postgres and Grails into Docker containers. The process that was used there was for the grails “development” setup. In this post we’ll discuss how to deploy your Grails application as a war file into a containerized Tomcat server.
Cats in Containers
This is easier than herding cats, I promise.
To get tomcat running as a docker container, create a Dockerfile similar to the following:
Once this is complete, build the docker image:
Now let’s give it a test run to make sure everything is working as expected:
Now cruise on over to
http://localhost:8080/ in a web browser and you’ll see something akin to the following:
Now shut it down and let’s prepare to make it more useful:
Isn’t this great?
Now let’s create a
tomcat-users.xml file giving permissions to a user so we can interact
with the manager-gui. Create a a file called
tomcat-users.xml alongside your Dockerfile
containing the following (please don’t use admin/admin in your version…):
Now let’s update the Dockerfile to copy this file over to the container. Add the following:
Now let’s build this new container and kick it off:
Now navigate over to
http://localhost:8080/manager/html with your browser and you’ll
be presented with this beautiful management gui:
This interface allows us to upload the WAR generated from our grails project!
… at least it claims as much. In practice I was unable to get upload a war file, as the connection would instantly be reset. I did a bit of digging but wasn’t able to sort this out. Instead, let’s try another path. We’ll make a volume for tomcat’s webapps directory, map it to a local directory, and deploy our war by simply copying it over!
First, let’s nuke that last mistake:
Now update your Dockerfile to include the following:
Now we rebuild and kick it off again:
The run command above will bind
/my/local/war/path to the tomcat webapps
directory. Now we can build our Grails war (using a gradle task or whatever you
prefer) and simply copy it over to this location:
At this point, you can monitor the output in your tomcat container as the war is unpacked and deployed.
Things are getting serious now
Oh…. wait… what?!
You may have noticed that the app isn’t loading, and on the tomcat STDOUT, there’s some logging similar to this:
12-Apr-2015 23:56:16.697 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive /usr/local/tomcat/webapps/petclinic-0.1.war 12-Apr-2015 23:56:25.288 INFO [localhost-startStop-1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. log4j:WARN No appenders could be found for logger (org.codehaus.groovy.grails.commons.cfg.ConfigurationHelper). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 12-Apr-2015 23:56:37.443 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.startInternal Error listenerStart 12-Apr-2015 23:56:37.476 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.startInternal Context [/petclinic-0.1] startup failed due to previous errors 12-Apr-2015 23:56:37.499 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive /usr/local/tomcat/webapps/petclinic-0.1.war has finished in 20,803 ms
Note that your output may be somewhat different (I’m not actually using the petclinic app for this).
To track down the root cause, peer into the ‘localhost’ log on the container:
Inspecting this log file, you’ll find a complaint about
petclinicDatasource. The issue here is the grails
which uses a JDNI definition of the form:
To fix this, we need to define that resource in the tomcat context. Let’s create a file called
context.xml alongside the Dockerfile.
As you can see above, we’ve added the
Resource block to define the
petclinicDatasource resource. This is,
of course, a jdbc connection. You may notice that I used
database as the hostname in the connection string.
More on that later… First let’s update the Dockerfile:
Note that I’m also adding the postgresql driver here - we need that as the resource is defined as
a postgresql connection. This also displays the
ADD directive’s URL version. Pretty handy!
Now we need to define the
database host that we used in the JDBC info above. This can be accomplished
by adding a host to the container’s
/etc/hosts file, which is done via the
Let’s rebuild and run again!
add-host switch above adds an entry defining
database as the ip address listed.
You’ll want to change this to your machine’s ip address. There are dozens of ways to grab
the ip address when needed here, so don’t feel like you need to hardcode it.
Great! Now the database is connected!
Actually, I needed to update my local postgresql to bind to the ip address above, edit the HBA file to accept connections from the docker machine, and update iptables to allow traffic from the docker containers (cidr range 172.17.0.0/24) to my local postgresql instance.
I won’t detail all of that now, but here’s the condensed version:
- Edit $PG_HOME/postgresql.conf
listen_addressesto include the relevant interface
- Add an entry in $PG_HOME/pg_hba.conf for your db user:
host petclinic grails 172.17.0.0/24 md5
- Restart postgresql
- Add an ip tables rule:
iptables -I INPUT -p tcp --dport 5432 --source 172.17.0.0/24 -j ACCEPT
At this point, you should be able to restart your container and achieve success!
Note - I had to fix some database migration issues on my actual project here. Check the logs on the container for more information if your application fails to start!
I guess it’s a little late to say voilà! at this point.
For the final version of the files, take a look at the blog-snippets repo.