Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Heroku - Can I call Maven from Procfile?

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?

like image 252
aroth Avatar asked Nov 09 '11 03:11

aroth


People also ask

What is the Heroku Maven plugin?

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.

What is procfile in Heroku?

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. ...

What is the Heroku Java deployment plugin?

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

How do I run a process on Heroku?

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).


2 Answers

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
like image 106
James Ward Avatar answered Sep 19 '22 05:09

James Ward


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:

  1. It tells Tomcat to use the configuration and deployment artifacts that cargo packaged as part of your build, by setting CATALINE_BASE to point at the correct place.
  2. It overwrites the default server.xml and persistence.xml files with their heroku-specific variants.
  3. It marks all the startup scripts in the Tomcat instance that cargo installed as part of the build as executable.
  4. It specifies values for http.port and hibernate.connection.url based upon the environment variables provided by the Heroku platform.
  5. Finally, it runs Tomcat. Note that you cannot use 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.

like image 38
aroth Avatar answered Sep 22 '22 05:09

aroth