I'm investigating Heroku as a platform and am trying to get a basic Java webapp to run on it. The webapp already builds and runs with Maven (using Tomcat and the cargo-maven-plugin), so I figured this should be a breeze given that Heroku uses Maven to manage its installation/deployment tasks.
That's not the case however, as I'm unable to get the thing to actually start up. My Procfile
has the following in it:
web: sh ./startServer-heroku.sh
And the startServer-heroku.sh
is just:
mvn clean install cargo:start -Dcargo.maven.wait=true
This works fine when I test locally using the foreman start
command, as described in the Heroku tutorial docs. But when I try it on the actual Heroku server, I get the following log messages:
2011-11-09T02:30:27+00:00 heroku[web.1]: State changed from created to starting
2011-11-09T02:30:27+00:00 heroku[slugc]: Slug compilation finished
2011-11-09T02:30:33+00:00 heroku[web.1]: Starting process with command `sh ./startServer-heroku.sh`
2011-11-09T02:30:33+00:00 app[web.1]: ./startServer-heroku.sh: 1: mvn: not found
2011-11-09T02:30:33+00:00 heroku[web.1]: Process exited
2011-11-09T02:30:34+00:00 heroku[web.1]: State changed from starting to crashed
It appears that mvn
is nowhere to be found on the system's PATH
, so the command is failing.
Is it possible to invoke mvn
from the Heroku Procfile
? And is there anywhere that has a definitive list of commands that are and are not available from the Procfile
?
The Heroku Maven plugin utilizes this API to provide direct deployment of prepackaged standalone web applications to Heroku. This may be a preferred approach for applications that take a long time to compile, or that need to be deployed from a Continuous Integration server such as Travis CI or Jenkins.
The Procfile 1 Procfile naming and location. The Procfile is always a simple text file that is named Procfile without a file extension. ... 2 Procfile format. ... 3 Developing locally. ... 4 Deploying to Heroku. ... 5 Scaling a process type. ... 6 More process type examples. ... 7 Procfile and heroku.yml. ...
This plugin is used to deploy Java applications directly to Heroku without pushing to a Git repository. It uses Heroku's Platform API . This is can be useful when deploying from a CI server, deploying pre-built JAR or WAR files. The plugin has two main goals: In addition to those two main goals, three additional goals are available: Maven 3.5.x
The simplest manifestation of the process model is running a one-off process. On your local machine, you can cd into a directory with your app, then type a command to run a process. On Heroku's Cedar stack, you can use heroku run to launch a process against your deployed app's code on Heroku's execution environment (known as the dyno manifold).
Maven is not in the slug that gets deployed to dynos. It's only available at compile time. One option for dealing with this is to use the appassembler-maven-plugin
and jar
packaging to generate a start script:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>appassembler-maven-plugin</artifactId>
<version>1.1.1</version>
<configuration>
<assembleDirectory>target</assembleDirectory>
<programs>
<program>
<mainClass>foo.Main</mainClass>
<name>webapp</name>
</program>
</programs>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>assemble</goal>
</goals>
</execution>
</executions>
</plugin>
Then the Procfile
would be:
web: sh target/bin/webapp
Another option is the maven-dependency-plugin
and war
packaging:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-runner</artifactId>
<version>7.5.3.v20111011</version>
<destFileName>jetty-runner.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
With a Procfile
of:
web: java $JAVA_OPTS -jar target/dependency/jetty-runner.jar --port $PORT target/*.war
James's answer provides good instructions for getting Jetty to work on Heroku, and his comment includes a link to a good reference on using embedded Tomcat. But it is also possible to run the standard, standalone version of Tomcat on Heroku. Here's how I was able to get it to work:
First, set up your POM to install and configure Tomcat as part of your build, and also to deploy your application to the installed Tomcat instance:
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<container>
<containerId>tomcat6x</containerId>
<zipUrlInstaller>
<url>http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.18/bin/apache-tomcat-6.0.18.zip</url>
</zipUrlInstaller>
<dependencies>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
</dependencies>
</container>
<configuration>
<type>standalone</type>
<deployables>
<deployable>
<groupId>com.yourcompany.name</groupId>
<artifactId>yourArtifact</artifactId>
<type>war</type>
<properties>
<context>ROOT</context>
</properties>
</deployable>
</deployables>
</configuration>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>install</goal>
<goal>configure</goal>
<goal>deploy</goal>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
Next, create a trimmed-down server.xml
file that will work on Heroku:
<?xml version='1.0' encoding='utf-8'?>
<Server port="-1">
<Listener className="org.apache.catalina.core.JasperListener" />
<Service name="Catalina">
<Connector port="${http.port}" protocol="HTTP/1.1" connectionTimeout="20000"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/>
</Engine>
</Service>
</Server>
...this is necessary because your Heroku app is only allowed to bind to a single port (which changes each time a new instance is created, and is specified in the $PORT
environment variable). Attempting to bind to any other port will crash your app. As the port is dynamic, it must be passed to server.xml
via the http.port
system property, but we'll get to that later.
While you're at it, also create a persistence.xml
file that will work with Heroku:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="quiz_devel">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.archive.autodetection" value="class"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show.sql" value="true"/>
<property name="hibernate.c3p0.acquire_increment" value="1"/>
<property name="hibernate.c3p0.idle_test_period" value="10"/>
<property name="hibernate.c3p0.max_size" value="20"/>
<property name="hibernate.c3p0.max_statements" value="40"/>
<property name="hibernate.c3p0.min_size" value="1"/>
<property name="hibernate.c3p0.timeout" value="30"/>
</properties>
</persistence-unit>
</persistence>
Note that there is no hibernate.connection.url
specified here. This is because Heroku specifies the database url that your app should use in the $DATABASE_URL
environment variable.
Now it's time to create a simple shell script that configures the environment and sets everything up so that Tomcat can actually run:
#point to the correct configuration and webapp
CATALINA_BASE=`pwd`/target/cargo/configurations/tomcat6x
export CATALINA_BASE
#copy over the Heroku config files
cp ./server-heroku.xml ./target/cargo/configurations/tomcat6x/conf/server.xml
cp ./persistence-heroku.xml ./target/cargo/configurations/tomcat6x/webapps/ROOT/WEB-INF/classes/META-INF/persistence.xml
#make the Tomcat scripts executable
chmod a+x ./target/cargo/installs/apache-tomcat-6.0.18/apache-tomcat-6.0.18/bin/*.sh
#set the correct port and database settings
JAVA_OPTS="$JAVA_OPTS -Dhttp.port=$PORT -Dhibernate.connection.url=$DATABASE_URL"
export JAVA_OPTS
#start Tomcat
./target/cargo/installs/apache-tomcat-6.0.18/apache-tomcat-6.0.18/bin/catalina.sh run
This is doing a number of things:
CATALINE_BASE
to point at the correct place.server.xml
and persistence.xml
files with their heroku-specific variants.http.port
and hibernate.connection.url
based upon the environment variables provided by the Heroku platform.startup.sh
to do this, as startup.sh
will launch Tomcat in a new process and then terminate. Heroku doesn't understand this, and thinks that the termination of startup.sh
is the termination of the Tomcat process.Finally, the last step is to set up your Procfile
to call your startup script, something along the lines of:
web: sh startServer-heroku.sh
With this approach you can have a project that is compatible with Heroku, while still retaining its ability to run standalone as a standard Java webapp.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With