ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Follow publication

Deploying a full stack Scala application on fly.io

A while back, I was happy to discover that Heroku allowed one to host, completely for free, a small application on their dynos. Recently, though, I was less happy to learn that they were decommissioning them, so that people were asked to update to paid plans.

Fortunately, one alternative immediately offered itself, namely fly.io. In this blog post, I will present a possible setup of a web application where both the frontend and the backend are developed in Scala, and which can be deployed easily to fly.io, completely for free. For the impatient, you can find the result on GitHub.

For this small example, I made some choices of technology that don’t matter much. The focus should be on the architecture of the project. For completeness, though, let us mention these choices. The server is implemented using cask, the frontend web framework is Laminar. We use vitejs to manage the packaging of our frontend, and sbt-assembly to create a fat jar. Hot reloading the backend server is possible using sbt-revolver. Finally, we use circe to serialize our data between frontend and backend.

Project structure

This project is composed of a single sbt project, with three submodules. One shared submodule cross-compiling to both JVM bytecode and JavaScript. That submodule contains all the core logic that both the frontend and backend should know about. Then there is one submodule “server” containing the backend code (purely JVM), and one submodule “frontend” containing the frontend code (purely JS).

The general structure thus looks like this:

build.sbt
server/
├─ src/
shared-logic/
├─ src/
frontend/
├─ vite.config.js
├─ package.json
├─ src/
shared-logic/
├─ shared/src/
Dockerfile
fly.toml

The backend is a simple JVM process, and hence all dependencies are handled by sbt — there is nothing more to do there. The frontend, however, lives in the npm ecosystem, and therefore must manage npm dependencies in addition to usual Scala dependencies. This job is given to Vite. In particular, that means that downloading npm dependencies must be done “manually” by running npm ci in the frontend directory.

Development Setup

Our goal is to deploy our application to fly.io. But before we can do that, it would be nice if we can develop it. When developing a web application with a Single Page Application as frontend, it is important to have effective refresh cycles when we code.

Vite has everything we need to achieve that. It has a development server that will automatically refresh our browser page on every source change. In this case, a “source change” is actually a change in the compiled JavaScript file from Scala.js. Fortunately, having this compiled file to refresh automatically on source change can be done with the command ~frontend/fastLinkJS inside sbt. And the dev server from vite can be run with npm run dev in the frontend directory.

It is also nice if the server backend can refresh automatically on source change. To that end, we use the sbt-revolver plugin. With it, we can run ~server/reStart in sbt.

If you are not familiar with web development, you can wonder how the frontend (and its dev server running on port 3000) knows when to contact the backend (running on port 9000). The Vite is configured in such a way that each request starting with /api is proxied to the backend.

To sum up, running the application in development requires to run these three commands (in three different terminals):

sbt ~frontend/fastLinkJS
sbt ~server/reStart
cd frontend && npm run dev

And then you can visit http://localhost:3000/static

Production Setup

Deploying the application on fly.io requires a few steps: packaging the frontend part into static files that a browser can consume, packaging the server part into a ready-to-go fat jar (containing the packaged frontend), and finally put it in a Docker image with a suitable JVM. Note that you don’t even need to have Docker installed, fly.io will take care of it for you.

If you don’t know Docker, you can imagine that you are given a brand new computer with nothing in it, and you have to describe all the things that you want for your application. This description includes the OS— usually a thin Linux distribution — , the programs that you want installed, the files that you need and finally a command to run to execute your program. In our case, we really don’t need much: a JVM and a fat jar, which we run with java -jar thejar.jar. These instructions are gathered in a Dockerfile.

Packaging the frontend is taken care of by Vite. Once the frontend submodule is compiled with sbt frontend/fullLinkJS, we can run npm run build (in the frontend directory) and then copy-paste the generated files inside the resources of the backend.

Packaging the backend uses sbt-assembly, so that we need to run sbt server/assembly to get the fat jar (which, in the project, is configured to be called app.jar). Then we can copy-paste that fat jar inside the dist folder, where our Dockerfile will expect to find it.

Finally, the Dockerfile will use a small Linux distribution with a Zulu openjdk, add the fat jar, and run it on port 8080 with the isProd setting set to true. This setting is used by the server to use the host 0.0.0.0 instead of localhost. That is required because, even if you launch a Docker container on your computer, reaching to it via http is still considered “outside traffic”, and will not respond to localhost.

Now, instead of doing these steps manually, the project has an sbt task packageApplication that will do all of that for us. Hence, if you run sbt packageApplication, you will end up with a file dist/app.jar, ready to go.

Note that we configured the backend to serve all static files from the static folder, which is reflected in vitejs configuration as well.

Fly.io Setup

The complete fly.io configuration is contained inside the fly.toml file, which you can generate for your own application with fly launch. (For that, you need to have flyctl installed.) You will be asked a bunch of questions (in particular we said “no” to a postgres or a redis database, and we selected Amsterdam as location, since it is the closest to Belgium).

Because we have a Dockerfile, and because we made our fat jar with sbt packageApplication, we can run fly deploy and the magic will operate. After it is finished, we can directly open a browser to its location with fly open, and we can even monitor it on the fly.io dashboard.

Cherry on the cake: the GitHub action

The code for our application is hosted on Github and, as you may know, we are allowed to add ci/cd pipelines in the form of Github actions. Fly.io even has an action that we can use. In our case, we need to mix it with something that installs a jdk and sbt, and something that installs npm. You can see it in detail here.

Every time we commit to the “publish” branch, our application will be automatically re-deployed to fly.io!

Closing words

We saw how we can, going from scrach, arrive to a setup to build web applications entirely in Scala, and deploying them for free on the fly.io platform. Next time, we will do the same but with a node.js backend, also written in Scala.

As much as my previous Heroku blog post was a letter to me in the future (a future that ended on November 28, 2022), this blog post is a letter to me in the new future, that I hope will last even longer. That said, I hope it can be useful for you!

Want to read more about full stack Scala and fly.io? Check out this masterpiece by Anton Sviridov!

Sign up to discover human stories that deepen your understanding of the world.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Written by Antoine Doeraene

Mathematician, Scala enthusiast, father of two.

No responses yet

Write a response