Deploying to Tomcat From Maven Builds

It's quite common to deploy web applications from a nightly continuous integration job to a testing server. In my case, I want to deploy a Java web application to a remote Tomcat server from a Maven build. Enter Cargo, a collection of tools for controlling Java EE containers like Tomcat, JBoss, or Jetty. In this article, we'll be using the cargo-maven2-plugin that is part of the suite.

Before we begin let's first create a Tomcat user with appropriate privileges to enable remote deployment. In this example, we'll be using the following tomcat-users.xml:

<tomcat-users>
  <role rolename="manager"/>
  <user username="deploy" password="deploy-passwd" roles="manager"/>
</tomcat-users>

For the plugin to work, we have to configure a few things: The type of the Container (Tomcat 6 in our case), location and credentials of the Manager application, and the information which Maven artifact to deploy.

Here's a configuration snippet to add to our project/build/plugins element inside the POM:

<plugin>
  <groupId>org.codehaus.cargo</groupId>
  <artifactId>cargo-maven2-plugin</artifactId>
  <version>1.0.6</version>
  <configuration>
    <container>
      <containerId>tomcat6x</containerId>
      <type>remote</type>
    </container>
    <configuration>
      <type>runtime</type>
      <properties>
        <cargo.tomcat.manager.url>http://example.invalid:8080/manager</cargo.tomcat.manager.url>
        <cargo.remote.username>deploy</cargo.remote.username>
        <cargo.remote.password>deploy-passwd</cargo.remote.password>
      </properties>
    </configuration>
    <deployer>
      <type>remote</type>
      <deployables>
        <deployable>
          <groupId>${groupId}</groupId>
          <artifactId>${artifactId}</artifactId>
          <type>war</type>
          <properties>
            <context>${artifactId}</context>
          </properties>
        </deployable>
      </deployables>
    </deployer>
  </configuration>
</plugin>

Of course, the deployable doesn't have to be the current project's artifact, it could be anything else. This is very useful for setting up an integration test project that deploys some web application and runs tests against it (see this blog posting for more information). For REST-style web services, we could use my jsystest package or build our own matchers. For interactive web applications, there's probably something based on Selenium that we could use.

After the plugin configuration is done, we can deploy the application from the command line:

mvn cargo:deploy

Note that cargo won't deploy to an already existing context, you have to run mvn cargo:undeploy before. The command mvn cargo:redeploy is very handy in this case because it undeploys the existing application if there is one.

Remote deployment is pretty nice, but it has one drawback. As opposed to Cargo local deployment, there is no way to restart the container, which is unfortunate if the web application has memory leaks that result in OutOfMemoryExceptions after a few deployments. This is often caused by leftover ThreadLocal state in conjunction with thread pools which may be a problem in a library. In cases like this, the best alternative - except for fixing the memory leak - is to restart Tomcat regularly from a cron job.

social