Skip to content

Instantly share code, notes, and snippets.

@fonsp
Last active October 29, 2023 07:55
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save fonsp/38965d7595a5d1060e27d6ca2084778d to your computer and use it in GitHub Desktop.
Save fonsp/38965d7595a5d1060e27d6ca2084778d to your computer and use it in GitHub Desktop.

Deploying Julia on heroku

Let's assume that you can run your web server on your own computer, and that you can open it in your own browser (through localhost or 127.0.0.1). This guide will go through the steps of putting that app online!

This guide will be based on a hello world sample project that uses Genie.jl, but the steps from this guide apply to any Julia web framework.

heroku has tons of features, but for a simple app, we only need the basics. In particular, we do not need the heroku command line, we can do everything through the online GUI.

Basics

heroku uses git for deployment: to package your app, you create a git repository, and to put a new version of your app online, you push to the git repository. If you already know git, then you now know how to manage a web server! It's like GitHub pages, but more powerful (and more complicated). Read heroku's introduction for a basic, but more technical, introduction.

So heroku apps are just git repositories! A Julia git repository needs some special files to work with heroku. You need to tell it how to set up the environment, and how to start your app.

  • /Manifest.toml - see the Pkg docs to learn who, what and why. Using only a Project.toml is not recommended!
  • /Procfile - a single line that starts with web: , followed by the shell command to run the server:
  • a script_to_launch_server.jl file that takes the port as a command line argument. (Use parse(Int, ARGS[1]) inside Julia to get the port number, and give that to your web package.) This is the file that you run from /Procfile.

/ denotes the root of the git repository

That's it!

The end of this guide contains some tips about package precompilation, to improve startup time and RAM usage.

Steps

Let's start by forking the sample repository. Even if you already have your own project ready, it might be useful to experiment with the sample repository first, and repeat for your own project afterwards.

heroku (2) heroku (3)

Create a heroku account, and log in to the dashboard. Let's create that app!

heroku (4) heroku (5)

You are greeted with the dashboard for your app. Go to Settings.

heroku (5a) heroku (5b)

Scroll down, and Add buildpack.

heroku (5c)

Paste the link to a Julia buildpack:

https://github.com/fonsp/heroku-buildpack-julia

This is a heroku-recognised shell script that downloads and installs Julia. (heroku computers have nothing installed out of the box.)

This buildpack is my fork of Optomatica/heroku-buildpack-julia, I go into their differences at the end of this guide. In these screenshots I used the original buildpack, but I recommend using the fork (fonsp/heroku-buildpack-julia) instead.

heroku (5d) heroku (5e)

Scroll up, and go to Deploy.

heroku (5f)

Choose GitHub as the deployment method. This means that you update your app simply by pushing new commits to your GitHub repository. heroku uses a webhook to know about new commits instantly.

heroku (6)

Search for your repository, and click Connect

heroku (7)

Scroll down, and Enable automatic deploys

heroku (12)

We need to start the first deployment manually, by clicking Deploy branch. All future commits will be deployed automatically.

heroku (13)

Scroll up, and go to Activity.

heroku (13a)

We see that our deployment is currently setting up. It just started a new virtual machine, and it is currently running the buildpack - it is downloading and installing Julia.

heroku (14)

It should be ready in a couple of minutes. Click on Open app to go to your app's URL.

heroku (15)

It will take a while before you see the web page. Right now, Julia is processing the first request, which means that it is compiling the app's functions. This can take a long time if your app is complicated, but the second request will be fast! More about precompilation at the end of this guide!

heroku (17)

Deploying an update

When you make a commit (to the master branch), heroku will start deploying that new version. The old version will stay online while the new version is being deployed! You can check the status in the Activity tab.

heroku (18) heroku (19)

Precompilation

Heroku has some quirks that don't play nice with Julia's just-in-time compilation model:

  • automatic shutdown - heroku will shut down the app's server after 30 minutes of inactivity, and also once per day. If you visit the URL when the app is shut down, it will quickly start up a new server, but Julia might take a long time to process that first request. (You can prevent the inactivity shutdown by paying extra, but the daily reboot is always enabled.)
  • time limit - if Julia does not reply to the first HTTP request within 60 seconds (or something), heroku will decide that the app is broken, and shut it down.
  • memory limit - heroku servers only get 512MB of RAM (or 1024MB if you pay extra). Package compilation can cause a spike in memory, much higher than you would need for regular use.

These problems can be avoided by precompiling your app, which means that you move Julia's initial compilation to the build process. When the app server starts up, most of the precompilation will be done already. heroku's build servers also have a much higher memory limit, my tests show that 4GB of RAM usage is fine.

Build-time precompilation

The easiest step is to Pkg.precompile() your app's dependencies during the build step. If you followed this guide, and used the buildpack

https://github.com/fonsp/heroku-buildpack-julia

(a fork of this buildpack, which is again a fork of this buildpack)

then this is done automatically! Check heroku's build logs to see what's up.

warmup.jl

Pkg.precompile() will not precompile the Julia methods that your app will use, it only compiles the top-level statements inside packages, not function bodies. The app startup will be faster, but there is still a lot of compilation happening, and you might need to speed it up more.

The buildpack uses PackageCompiler.jl to precompile methods. (Learn how it works at the JuliaCon 2020 presentation.) To do so, it needs to know which functions will be called with which types as arguments. You can generate and provide this list yourself (by including /precompile.jl, see the PackageCompiler docs), but the custom buildpack has an easier way:

If the git repository contains a file /warmup.jl, then this file will be run during the app build, and all methods called (indirectly) by this file will be precompiled!

For example, if my app uses a function special_sqrt(x), where x can be an Int64 or a Float64, then my warmup.jl would look like:

special_sqrt(3)
special_sqrt(3.0)

If sepcial_sqrt uses external packages, then the used methods from those packages will be called indirectly by warmup.jl, so they will also be precompiled!

Hope this helps!

-fonsi

@nicholaskarlson
Copy link

This helped a lot. Thanks for the great post!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment