deployment

deployment

Learn how to build, package and deploy applications.

intro

Jooby applications are self-contained and they don’t run inside a container or Servlet environment.

Application resources (a.k.a conf files) are packaged within your app. Not only that, the server you pick is also packaged within your application.

Jooby lack of the concept of server/servlet container it just a raw/plain/normal Java application that can be launched via:

java -jar myapp.jar

Where myapp.jar is a fat jar. A fat jar is a jar with all the app dependencies, conf files and web server… all bundle as a single jar.

environments

The fat jar is nice when we need to deploy our application into one single environment, but what if we need to deploy to stage? or prod?

The way we deploy to a different environment is by creating a conf file (.conf or logback.xml) and adding the environment as suffix.

If you need to deploy to stage and prod then you probably need:

# dev environment
conf/application.conf
conf/logback.xml

conf/application.stage.conf
conf/logback.stage.xml

conf/application.prod.conf
conf/logback.prod.xml

The file jar contains everything your dev, stage and prod conf files.

Jooby is built around the environment concept, which means a Jooby app always known in which environment it runs.

The application.env property controls the environment, by default this property is set to dev (the unique and well known environment).

Suppose you need to deploy your awesome app into prod and you need to set/update a few properties. The application.conf file looks like:

...
aws.accessKey = AKIAIOSFODNN7EXAMPLE
aws.secretKey =  wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
...

For prod you have a new key pair… then all you have to do is to create a new file application.prod.conf and just add those new keys:

aws.accessKey = AKIAIOSFODNN7PROD
aws.secretKey =  wJalrXUtnFEMI/K7MDENG/bPxRfiCYPROD

TIP: You don’t have to add all the properties, just those that changes between environments.

run your app

In order to start your application in one specific environment, you need to set the application.env argument:

java myapp.jar application.env=prod

or using a shortcut, like:

java myapp.jar env=prod

or just:

java myapp.jar prod

Your application will run now in prod mode. It will find your application.conf and overrides any property defined there with those from application.prod.conf

It works for logback.xml too, if logback.[env].xml is present, then Jooby will use it, otherwise it fallbacks to logback.xml.

fat jar

This is the default deployment option and you (usually) don’t need to do anything if you created your application via maven archetype.

In order to create a fat jar, go to your project home, open a terminal and run:

mvn clean package

The jar will be available inside the target directory.

configuration

We use the maven-shade-plugin for creating the fat jar:

...
<build>
  <plugins>
    ...
    <!-- Build fat jar -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
    </plugin>
    ...
  </plugins>
</build>

Or the gradle-shadow-plugin:

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "com.github.jengelman.gradle.plugins:shadow:4.0.1"
  }
}

apply plugin: "com.github.johnrengelman.shadow"

If you created your application via maven archetype this setup is already present in your application.

run / start

Since everything was bundled into a single jar, all you have to do is:

java -jar myapp.jar [env]

Easy huh? No complex deployment, no heavy-weight servers, no classpath hell, nothing!

Your application is up and running!

stork

Build, package and distribute your application using Stork.

Stork is a collection of utilities for optimizing your after-build workflow by filling in the gap between your Java build system and eventual end-user app execution.

usage

Stork integration is provided via Maven Profiles.

  • Write your stork.yml launcher and save it in the src/etc directory

  • Open a console and type: mvn clean package

  • It builds a [app-name].zip file inside the target directory

profile activation

The stork Maven profile is activated by the presence of the src/etc/stork.yml file.

launcher configuration

You must name your launcher as stork.yml and save it inside the src/etc directory.

Maven properties defined here will be resolved and merged into the final output. Examples of these properties are: ${application.class}, ${project.groupId}, etc…

example

Here’s a simple example of the stork.yml launcher:

# Name of application (make sure it has no spaces)
name: "${project.artifactId}"

# Display name of application (can have spaces)
display_name: "${project.name}"

# Type of launcher (CONSOLE or DAEMON)
type: DAEMON

# Java class to run
main_class: "${application.class}"

domain: "${project.groupId}"

short_description: "${project.artifactId}"

# Platform launchers to generate (WINDOWS, LINUX, MAC_OSX)
# Linux launcher is suitable for Bourne shells (e.g. Linux/BSD)
platforms: [ LINUX ]

# Working directory for app
# RETAIN will not change the working directory
# APP_HOME will change the working directory to the home of the app
# (where it was intalled) before running the main class
working_dir_mode: RETAIN

# Minimum version of java required (system will be searched for acceptable jvm)
min_java_version: "1.8"

# Min/max fixed memory (measured in MB)
min_java_memory: 512
max_java_memory: 512

# Min/max memory by percentage of system
#min_java_memory_pct: 10
#max_java_memory_pct: 20

# Try to create a symbolic link to java executable in <app_home>/run with
# the name of "<app_name>-java" so that commands like "ps" will make it
# easier to find your app
symlink_java: true

capsule

Package and Deploy JVM Applications with Capsule.

A capsule is a single executable JAR that contains everything your application needs to run either in the form of embedded files or as declarative metadata. More at capsule.io

usage

Capsule integration is provided via Maven Profiles using the capsule-maven-plugin.

  • Write a capsule.activator file inside the src/etc directory

  • Open a console and type: mvn clean package

  • It builds a [app-name]-capsule-fat.jar file inside the target directory

It generates a fat capsule by default inside the target directory.

If you are a Gradle user, checkout the capsule gradle example.

profile activation

The capsule Maven profile is activated by the presence of the src/etc/capsule.activator file (file content doesn’t matter, just the file presence).

options

The integration provides the following defaults:

<properties>
  <capsule.resolve>false</capsule.resolve>
  <capsule.chmod>true</capsule.chmod>
  <capsule.trampoline>false</capsule.trampoline>
  <capsule.JVM-Args>-Xms512m -Xmx512m</capsule.JVM-Args>
  <capsule.types>fat</capsule.types>
  <capsule.caplets>Capsule</capsule.caplets>
</properties>

For example, if you need or prefer a thin capsule, follow these steps:

  • Open your pom.xml
  • Go to the <properties> section and add/set:
<properties>
  <capsule.resolve>true</capsule.resolve>
  <capsule.types>thin</capsule.types>
</properties>
  • Open a console and type: mvn clean package

You’ll find your thin capsule in the target directory.

docker

Docker is the world’s leading software containerization platform. You can easily run you Jooby app as a docker container. You need to have the docker engine installed.

usage

You need docker installed on the building machine.

Maven users have two options: one for a building a fat jar and one for building a stork distribution.

Gradle users might want to choose one of the available plugins.

fat jar

  • Write a src/etc/docker.activator file. File contents doesn’t matter, the file presence activates a Maven profile.

  • Open a terminal and run:

mvn clean package docker:build
  • Once finished, the docker image has been built and tagged as ${project.artifactId}.

  • You can now run the image with:

docker run -p 80:8080 ${project.artifactId}

The Maven profile triggers the spotify/docker-maven-plugin which generates a docker file. Please see the doc for more details.

stork

  • Write a src/etc/docker.stork.activator file. File contents doesn’t matter, the file presence activates a Maven profile.

  • Open a terminal and run:

mvn clean package docker:build
  • Once finished, the docker image has been built and tagged as ${project.artifactId}.

  • You can now run the image with:

docker run -it -p 80:8080 ${project.artifactId}

The Maven profile triggers the spotify/docker-maven-plugin which generates a docker file. Please see the doc for more details.

static resources

This isn’t a deployment option, just a way to collect static resources and deploy them somewhere else.

The static assembly let you collect and bundle all your static resources into a zip file.

This is nice if you want or prefer to serve static resources via nginx or apache.

usage

  • Write a assets.activator file inside the src/etc directory

  • Open a console and type: mvn clean package

  • Look for [app-name].static.zip inside the target directory

war?

This module exists for strict environments where the ONLY option is to deploy into a Servlet Container. It’s strongly recommended to avoid this and rather opt for using one of Netty, Jetty or Undertow.

usage

In order to deploy into a Servlet Container, you need to generate a *.war file. Here’s how:

  • Create a war.activator file in the src/etc directory.

  • Open a console and type: mvn clean package

  • Find the *.war file in the target directory.

limitations

  • web-sockets are not supported
  • some properties has no effect when deploying into a Servlet Container:
    • application.path / contextPath
    • appplication.port
    • max upload file sizes
    • any other server specific property: server., jetty., netty., undertow.

special note on contextPath

To avoid potential headaches, make sure to use the contextPath variable while loading static/dynamic resources (.css, .js, etc..).

For example:

<html>
<head>
  <link rel="stylesheet" text="text/css" href="{{contextPath}}/css/styles.css">
  <script src="{{contextPath}}/js/app.js"></script>
</head>
</html>

The expression: {{contextPath}} corresponds to the template engine (handlebars in that case) or ${contextPath} for Freemarker.

how does it work?

The presence of the src/etc/war.activator file triggers a Maven profile. The contents of the file doesn’t matter, it just needs to be present.

The Maven profile builds the *.war file using the maven-assembly-plugin. The assembly descriptor can be found here

web.xml

A default web.xml file is generated by the assembly plugin. This file looks like:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">
  <context-param>
    <param-name>application.class</param-name>
    <param-value>${application.class}</param-value>
  </context-param>

  <listener>
    <listener-class>org.jooby.servlet.ServerInitializer</listener-class>
  </listener>

  <servlet>
    <servlet-name>jooby</servlet-name>
    <servlet-class>org.jooby.servlet.ServletHandler</servlet-class>
    <load-on-startup>0</load-on-startup>
    <!-- MultiPart setup -->
    <multipart-config>
      <file-size-threshold>0</file-size-threshold>
      <!-- Default 200k -->
      <max-request-size>${war.maxRequestSize}</max-request-size>
    </multipart-config>
  </servlet>

  <servlet-mapping>
    <servlet-name>jooby</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>
max upload size

The default max upload size is set to 204800b (200kb). If you need to increase this, add the war.maxRequestSize property to pom.xml:

<properties>
  <war.maxRequestSize>1048576</war.maxRequestSize> <!-- 1mb -->
</properties>
custom web.xml

When the generated file isn’t enough, follow these steps:

  1. create a dir: src/etc/war/WEB-INF
  2. save a web.xml file inside that dir
  3. run: mvn clean package

crash

CRaSH remote shell: connect and monitor JVM resources via HTTP, SSH or telnet.</a>

dependency

<dependency>
 <groupId>org.jooby</groupId>
 <artifactId>jooby-crash</artifactId>
 <version>1.6.6</version>
</dependency>

usage


import org.jooby.crash;

{
  use(new Crash());
}

Just drop your commands in the cmd folder and CRaSH will pick up all them.

Now let’s see how to connect and interact with the CRaSH shell.

connectors

HTTP connector

The HTTP connector is a simple yet powerful collection of HTTP endpoints where you can run a CRaSH command:

{
  use(new Crash()
     .plugin(HttpShellPlugin.class)
  );
}

Try it:

GET /api/shell/thread/ls

OR:

GET /api/shell/thread ls

The connector is listening at /api/shell. If you want to mount the connector some where else just set the property: crash.httpshell.path.

SSH connector

Just add the crash.connectors.ssh dependency to your project.

Try it:

ssh -p 2000 admin@localhost

Default user and password is: admin. See how to provide a custom authentication plugin.

telnet connector

Just add the crash.connectors.telnet dependency to your project.

Try it:

telnet localhost 5000

Checkout complete telnet connector configuration.

web connector

Just add the crash.connectors.web dependency to your project.

Try it:

GET /shell

A web shell console will be ready to go at /shell. If you want to mount the connector some where else just set the property: crash.webshell.path.

commands

You can write additional shell commands using Groovy or Java, see the CRaSH documentation for details. CRaSH search for commands in the cmd folder.

Here is a simple ‘hello’ command that could be loaded from cmd/hello.groovy folder:

package commands

import org.crsh.cli.Command
import org.crsh.cli.Usage
import org.crsh.command.InvocationContext

class hello {

  @Usage("Say Hello")
  @Command
  def main(InvocationContext context) {
    return "Hello"
  }
}

Jooby adds some additional attributes and commands to InvocationContext that you can access from your command:

  • registry: Access to Registry.
  • conf: Access to Config.

Example:

package commands

import org.crsh.cli.Command
import org.crsh.cli.Usage
import org.crsh.command.InvocationContext

class HelloMyService {

  @Usage("MySerivce.doSomething")
  @Command
  def execute(InvocationContext context) {
    def registry = context.registry

    return registry.require(MySerivce.class).doSomething()
  }
}

routes command

The routes print all the application routes.

dev> routes
order method pattern             consumes produces name       source
-------------------------------------------------------------------------------------------------
0     GET    /shell/css/**       [*/*]    [*/*]    /anonymous org.jooby.crash.WebShellPlugin:41
0     GET    /shell/js/**        [*/*]    [*/*]    /anonymous org.jooby.crash.WebShellPlugin:42
0     GET    /shell              [*/*]    [*/*]    /anonymous org.jooby.crash.WebShellPlugin:45
0     GET    /api/shell/{cmd:.*} [*/*]    [*/*]    /anonymous org.jooby.crash.HttpShellPlugin:43
0     *      {before}/path       [*/*]    [*/*]    /anonymous app.CrashApp:21
0     *      {after}/path        [*/*]    [*/*]    /anonymous app.CrashApp:24
0     *      {complete}/path     [*/*]    [*/*]    /anonymous app.CrashApp:28
0     GET    /                   [*/*]    [*/*]    /anonymous app.CrashApp:31


method pattern consumes         produces
-------------------------------------------------
WS     /shell  application/json application/json

conf command

The conf tree print the application configuration tree (configuration precedence):

dev> conf tree
 merge of system properties
  org/jooby/crash/crash.conf @ file:/jooby-crash/target/classes/org/jooby/crash/crash.conf
   org/jooby/spi/server.conf @ file:/jooby-netty/target/classes/org/jooby/spi/server.conf
    org/jooby/mime.properties @ file:/jooby/target/classes/org/jooby/mime.properties
     org/jooby/jooby.conf @ file:/jooby/target/classes/org/jooby/jooby.conf: 1

The conf props [path] print all the application properties, sub-tree or a single property if path argument is present.

dev> conf props
name                                   value
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
mime.pgn                               application/x-chess-pgn
os.version                             10.11.6
mime.ttf                               font/truetype
err.java.io.FileNotFoundException      404
sun.cpu.isalist
mime.wtls-ca-certificate               application/vnd.wap.wtls-ca-certificate
mime.texinfo                           application/x-texinfo
runtime.concurrencyLevel               8
.....
dev> conf props application
name                       value
--------------------------------------------------------------------------------
application.tmpdir         /var/folders/9l/fyb_j4l553z6ql4443yttbj40000gn/T/app
application.version        0.0.0
application.ns             app
application.port           8080
application.charset        UTF-8
application.redirect_https
application.class          app.CrashApp
application.tz             America/Argentina/Buenos_Aires
application.numberFormat   #,##0.###
application.dateFormat     dd-MMM-yyyy
application.env            dev
application.host           localhost
application.name           app
application.path           /
application.lang           en-US
dev> conf props application.port
name             value
-----------------------
application.port 8080

fancy banner

Just add the jooby-banner to your project and all the CRaSH shell will use it.

{
  use(new Banner("crash me!"));

  use(new Crash());
}
telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
_____                        _____
__________________ __________  /_       ______ ___ ____ __  /
  ___/_  ___/  __ `/_  ___/_  __ \      _  __ `__ \  _ \_  / 
 /__    /     /_/ / (__  )   / / /        / / / / /  __//_/  
___/  _/     __,_/  ____/  _/ /_/       _/ /_/ /_/ ___/ _) v0.0.0

dev>

Simple and easy!!

conclusion

  • jar/capsule deployment makes perfect sense for PaaS like Heroku, AppEngine, etc…

  • stork deployment give you more control and the sense of a traditional Java Server with start/stop scripts but also the power of control the application logs at runtime.