Dokku is a self-hosted Platform-as-a-Service (PaaS) that offers a compelling alternative to popular PaaS solutions like Heroku. With built-in support for Linux containers, deploying an application on Dokku is straightforward. However, there is a lesser-known deployment method that involves sending a build artifact, such as a JAR package for Java apps, directly to Dokku.
This deployment method is useful when there is a need to quickly and frequently deploy the latest version of a custom application. By skipping the process of creating a container image, developers can focus on building the artifact for local development. This approach can be applied to packaged applications built with various programming languages, including Python, Java, JavaScript, PHP, etc.
To follow this process, it is necessary to have the packaged Java application in the form of an Uberjar, i.e. a JAR archive that contains all dependencies and can be executed by the JVM without requiring additional packages at runtime. The process assumes that Dokku has been installed on a machine named dokku.homelab.lan
, and the dokku
command is working properly:
$ dokku version
dokku version 0.29.4
Also, due to some heavy building that is going to happen on that Dokku machine, make sure there is an ample free capacity on the disk, ideally 8 GB or more (depending on the application).
If we are deploying an Uberjar, obviously that Uberjar needs to exist first. For this example, I am using an Uberjar from the open-source edition of Metabase (adjust things to suit your need). Note that Metabase is written in Clojure, not Java, though it runs on JVM. In theory, any other JVM languages (e.g. Kotlin, Scala, etc) can work as well.
$ curl -OL https://downloads.metabase.com/v0.45.2/metabase.jar
$ file ./metabase.jar
./metabase.jar: Zip archive data, at least v1.0 to extract, compression method=store
Two auxiliary files, a Procfile
and a Dockerfile
, are required. The Procfile
contains a single line of code that specifies how the application is executed, while the Dockerfile
details the construction of the container.
The first file, Procfile
, is this one-liner:
web: java -jar metabase.jar
The second file, Dockerfile
, is not too strange to those who are familiar with Docker:
FROM eclipse-temurin:17
WORKDIR /app
COPY . ./
RUN java -version
RUN ls -l /app
EXPOSE 3000
The base image is set to the latest Long-Term Support (LTS) version of OpenJDK v17 using the Eclipse Temurin distribution from Adoptium. The EXPOSE
line indicates the port Metabase uses, which is port 3000. The two optional RUN
lines are useful for debugging or resolving any issues that may arise.
Next, we package the necessary files into a tarball by executing the following commands:
$ tar cvf package.tar Procfile Dockerfile metabase.jar
$ file ./package.tar
./package.tar: POSIX tar archive (GNU)
Before sending the tarball to Dokku, we must create an application. This is achieved by executing the following commands:
$ dokku apps:create metabase
-----> Creating metabase...
-----> Creating new app virtual host file...
$ dokku proxy:ports-set metabase http:80:3000
dokku proxy:ports-set metabase http:80:3000
-----> Setting config vars
DOKKU_PROXY_PORT_MAP: http:80:3000
The last command maps the host’s port 80 to the container’s exposed port 3000. And now, the fun starts!
$ cat package.tar | dokku git:from-archive metabase --
-----> Fetching tar file from stdin
-----> Generating build context
Striping 0 worth of directories from tarball
Moving unarchived files and folders into place
-----> Updating git repository with specified build context
-----> Cleaning up...
-----> Building metabase from Dockerfile
-----> Setting config vars
DOKKU_DOCKERFILE_PORTS: 3000
Sending build context to Docker daemon 271.7MB
Step 1/12 : FROM eclipse-temurin:17
Digest: sha256:f6562feb32844d0059616d6e54c6cc3127ccf77fb594ccb98cc4279ca15887ed
Status: Downloaded newer image for eclipse-temurin:17
---> 1e117025f42d
Step 2/12 : WORKDIR /app
---> Running in 89d26eed69f3
---> db157924a857
Step 3/12 : COPY . ./
---> 59e836261c66
Step 4/12 : RUN java -version
---> Running in 21df4266e534
openjdk version "17.0.6" 2023-01-17
OpenJDK Runtime Environment Temurin-17.0.6+10 (build 17.0.6+10)
OpenJDK 64-Bit Server VM Temurin-17.0.6+10 (build 17.0.6+10, mixed mode, sharing)
---> 16d451db8f1a
Step 5/12 : RUN ls -l /app
---> Running in 829a2df4f10e
total 265328
-rw-r--r-- 1 root root 92 Jan 31 02:57 Dockerfile
-rw-r--r-- 1 root root 271686194 Jan 31 02:57 metabase.jar
-rw-r--r-- 1 root root 28 Jan 31 02:57 Procfile
---> 67b7b1179da7
Step 6/12 : EXPOSE 3000
Step 7/12 : LABEL com.dokku.app-name=metabase
Step 8/12 : LABEL com.dokku.builder-type=dockerfile
Step 9/12 : LABEL com.dokku.image-stage=build
Step 10/12 : LABEL dokku=
Step 11/12 : LABEL org.label-schema.schema-version=1.0
Step 12/12 : LABEL org.label-schema.vendor=dokku
Successfully built a246db231b6f
Successfully tagged dokku/metabase:latest
-----> Releasing metabase...
-----> Checking for predeploy task
No predeploy task found, skipping
-----> Checking for release task
No release task found, skipping
-----> Checking for first deploy postdeploy task
No first deploy postdeploy task found, skipping
-----> Deploying metabase via the docker-local scheduler...
-----> Configuring metabase.dokku.homelab.lan...(using built-in template)
-----> Creating http nginx.conf
Reloading nginx
=====> Application deployed:
http://metabase.dokku.homelab.lan
The log may appear lengthy, but the steps it displays should be straightforward. On the Dokku target machine, a container image is constructed using the information in the tarball. From that image, a container is created and deployed using the standard Dokku machinery. If all goes well, the application (Metabase in this instance) will be up and running at the specified hostname.
As you become more familiar with this method, sending build artifacts to Dokku after each change will become a natural part of your workflow!