Deploying Ratpack applications to Cloud Foundry

There is already a post by Tomas Lin on this topic but the method he describes no longer seems to work, as can be seen in the comments. Guillaume Laforge tried this way and was unsuccessful, his end result was to modify the Cloud Foundry Java buildpack and his demo app and instructions can be found here.

IMHO having to modify the Java buildpack or create a custom buildpack everytime a new Java framework needs to be deployed to Cloud Foundry is not a very good model. Especially when there are no out of the ordinary requirements to run. I spent some time understanding what the problems were and my preference for deploying Ratpack applications to Cloud Foundry is detailed in the rest of this post.

What is Cloud Foundry?

I'm not going to go into the details of Cloud Foundry or what a Platform as a Serive (PaaS) is but a couple of things are worth mentioning.

Firstly, there's cloudfoundry.org. This is the open source Platform as a Service. This is the platform you can contribute to, fork, clone, run locally, use to create your own public PaaS offering etc.

Secondly, there's cloudfoundry.com which more recently is being referred to as run.pivotal.io. But both sites co-exist. This is the hosted public PaaS offering from Pivotal that is built on the Cloud Foundry open source platform. There are other public PaaS offerings that are also built to run on Cloud Foundry. The two that I have used most often are App Fog and Anynines.

App Fog, formerly PHP Fog, contributed the PHP runtime to Cloud Foundry and is the most mature Cloud Foundry PaaS to my knowledge. It is out of beta and has a nice web console, pricing options and great support in my experience.

Anynines is based in the EU and still in beta. The guys there are amazingly helpful and very active on Twitter.

It's also worth mentioning that there are currently two versions of the Cloud Foundry platform too. App Fog is currently running v1, Pivotal and Anynines v2.

Packaging your Ratpack application

Before you can deploy your application you need to package it for deployment. Ratpack gives you two options for this:

  1. Distribution ZIP - you can run the gradle command ./gradlew distZip and a full distribution ZIP archive including runtime libraries and OS specific run scripts will be created in the folder ./build/distributions. This is standard Gradle functionality provided by the Application plugin.

    If you open the zip file you will see a folder named after your application and inside that app, bin and lib folders. The bin folder contains the OS specific run scripts.

  2. Fat JAR - you can run the gradle command ./gradlew fatJar and your application will be packaged as a single, runnable jar file. Including all of it's dependencies. The jar file will be created in ./build/libs and can be run using java -jar. This functionality is provided by the Ratpack Gradle plugin.

Depending on the version of Cloud Foundry your provider is running and even who your provider is depends on what packaging option you need to use. But the two options available to you should cover all bases.

Deploying to Cloud Foundry v2

To do this you will need an account with Pivotal, Anynines or another version 2 provider. You will also need to have the command line interface (CLI) installed. The examples in this post assume version 5 of the CLI.

We will also be using the example-books application.

Distribution ZIP

This method works with both Pivotal and Anynines, I cannot see any reason why it wouldn't work with any provider.

  1. Clone the example-books repository, cd to it and run the distZip Gradle command.

    git clone git@github.com:ratpack/example-books.git
    cd example-books
    ./gradlew distZip

  2. Create a file called manifest.yml in the application root folder with the following content:

    ---
    applications:
    - name: myApp
      memory: 512M
      instances: 1
      host: myApp
      domain: cfapps.io
      path: ./build/distributions/example-books.zip
      command: ./example-books/bin/example-books
      env:
        JAVA_HOME: /home/vcap/app/.java
        EXAMPLE_BOOKS_OPTS: -Dratpack.port=$PORT

  3. The manifest file contains the deployment instructions for Cloud Foundry. Details of what you can set can be found here. The points of interest in our file are:

    • host and domain - If your combination of domain and host are already taken then you will be promted for different ones.
    • path - This the path to our deployment artefact, relative to the current working directory.
    • command - This is the run command Cloud Foundry will use. It is set to the OS specific script.
    • JAVA_HOME - If Cloud Foundry does not recognise your application then despite downloading and making a Java runtime available it does not set the JAVA_HOME environment variable for you. n.b. different providers may put the runtime in different locations e.g. for Anynines the value needs to be set to "/home/vcap/app/.jdk". You will be able to see where as part of the deployment output, it will say something like Expanding JRE to .java or Unpacking JDK to .jdk>
    • EXAMPLE_BOOKS_OPTS - The OS specific run scripts created by Gradle allow us to set JVM args via a variable in the format <APPNAME>_OPTS. We will take advantage of this to tell Ratpack what port to run on by mapping the Cloud Foundry generated port for our app (exposed as $PORT environment variable) to the ratpack.port system property. If you also have ratpack.port in ratpack.properties then this will be overriden by the system property. n.b. you can set any other JVM args you need via this variable too

  4. Set the target correctly for your provider

    For Pivotal use

    cf target api.run.pivotal.io

    For Anynines use

    cf target api.de.a9s.eu

  5. Login
    cf login --email myemail --password mypassword

  6. Push your application
    cf push myApp

Fat JAR

This method currently does not work with Pivotal. There may be other providers it doesn't work with too. The reason is that the CLI scans every file in the JAR to see whether it was previously uploaded. If it was then it doesn't need to upload it again. Pivotal uses S3 blob store as part of this process and the upload times out. It doesn't with Anynines but the deploy time does appear much slower than if you use the Distribution Zip method.

The steps are the same as for Distribution Zip above except in step 1. you need to run ./gradlew fatJar instead of ./gradlew distZip and in step 2. the contents of manifest.yml are different. They should be:

---
applications:  
- name: myApp
  memory: 512M
  instances: 1
  host: myApp
  domain: de.a9sapp.eu
  path: ./build/libs
  command: java $JAVA_OPTS -Dratpack.port=$PORT -jar example-books-fat.jar

Note here that we don't need to set any environment variables as we are able to set our JVM args as part of the run command.

Deploying to Cloud Foundry v1

To do this you will need an App Fog account or an account with another v1 based PaaS. From here on I will assume you are using App Fog and have installed the command line interface (CLI).

We will also be using the example-books application.

Fat JAR

  1. Clone the example-books repository, cd to it and run the fatJar Gradle command.

    git clone git@github.com:ratpack/example-books.git
    cd example-books
    ./gradlew fatJar

  2. Create a file called manifest.yml in the application root folder with the following content:

    ---
    applications:
      build/libs:
        name: myApp
        framework:
          name: standalone
          info:
            mem: 64M
            description: Standalone Application
            exec: 
        runtime: java
        command: java $JAVA_OPTS -Dratpack.port=$PORT -jar example-books-fat.jar
        infra: eu-aws
        url: myApp.eu01.aws.af.cm
        mem: 512M
        instances: 1

  3. Login
    af login --email myemail --password mypassword

  4. Push your application
    af push myApp

Summary

Deploying Ratpack applications to Cloud Foundry without making changes to the existing Java buildpack or creating a custom buildpack is pretty straightforward. This would be made easier if JAVA_HOME was automatically set and you didn't have to work out what it was. I have filed an issue for this.

If you have any problems with your deployment I'll be happy to help. Just post a comment to this post or use the Ratpack forum.