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 thetarget
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 thesrc/etc
directory -
Open a console and type:
mvn clean package
-
It builds a
[app-name]-capsule-fat.jar
file inside thetarget
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 thesrc/etc
directory -
Open a console and type:
mvn clean package
-
Look for
[app-name].static.zip
inside thetarget
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 thesrc/etc
directory. -
Open a console and type:
mvn clean package
-
Find the
*.war
file in thetarget
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:
- create a dir:
src/etc/war/WEB-INF
- save a
web.xml
file inside that dir - 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.