Deno on Fly using Buildpacks

We really like Deno at Fly. It really is a better Node and we’d love people to build more with it. Our plan? “Let's make building and deploying Deno apps on Fly as simple as other languages”. Now, you can configure, build and deploy Deno code in just two commands, and we’d like to show you how we did it.

What’s Deno?

The website bills Deno as a secure runtime for JavaScript and TypeScript. In practice, it’s like working with a streamlined Node where TypeScript is the norm, with a solid Rust foundation. It also incorporates many of the lessons learned over Node’s development and growth.

As we write this, Deno is heading rapidly towards its first 1.0 release candidate and we are really looking forward to a post-1.0 Deno. We believe as more people discover Deno’s elegance as a platform it’ll become much more popular. So how do you build a Deno app for Fly now?

Straight to the chase

  • Name your app’s entry point server.ts
  • Create your Fly app with flyctl init --builder flyio/builder
  • touch .config to create an empty ".config" file to go with your app
  • flyctl deploy and watch it all build and deploy

That’s it.

Behind the builder

The “chase” above was notably short. How? Let's look at what’s powering this build process. Fly’s builder support landed in early April and is based on the Cloud Native (CNB) specifications and implementations for building cloud native container images.

A Cloud Native build is made up of stacks, builders and buildpacks which come together to make a repeatable build process, for different languages and frameworks. Used together, they deliver container images that are ready to run. For the Deno builder, we created our own stack, builder and buildpack to build Deno.


Everything in CNB is built on a “stack”, the base operating system in which the builders operate. For the Fly stack, we selected Ubuntu bionic, added in curl and unzip utilities (to support the Deno installer) and built the three images - base, build and run - which will be used in the subsequent steps.


A builder in CNB is a container loaded with all the OS level tooling needed to construct an image. It’s fundamentally an OS image of some form. It’s not tied to any specific container platform such as Docker, it’s designed to run wherever you can run a container.

That said, the developers do have the pack commmand that uses Docker to run these containers - especially useful on Windows and macOS where there is no native Linux container support.

The builder brings up a container running the build stack and then steps through the buildpacks associated with the builder to work out which build script to run in that container.


The buildpacks are smaller bundles of scripts (or Go programs) which have two jobs, detect and build.


The detect script tries to work out if the directory contents it has been given are appropriate to build with its build script. For example, a detect script for Ruby would look for a Gemfile and go “aha! this is the song of my people! run my build script”.

Now, Deno doesn't have an obvious packaging file like Ruby or well, anything else, due to it being so self-contained. So, we made a rule. If there’s a server.ts file in the directory, we’ll assume it's a Deno application and signal to run our build script.


The build script runs in the Builder’s container and does the work of assembling tools and building the layers to create our image. In the case of our Deno buildpack, that includes downloading Deno into the image and running the Deno deno cache command so all the dependencies are ready to run. Finally, it writes out a set of launch commands to start up the application.


There's a number of settings that can be passed to the Deno buildpack to control how the application is run. They are kept in the .config file as a set of key values. You can run with an empty .config file to get defaults, but you do currently need to have a .config file present.

First up is the permissions setting. Deno restricts access to resources by default. You have to specify which resources are available to any program with --allow-* command-line flags. The permissions setting contains the command-line arguments you’d expect to be added to the Deno command-line. You are most probably always going to have to set the permissions setting. In the example app, this file contains:


Which allows network access. If there’s no permissions setting, currently we default to no permission flags, in step with the default Deno experience. You can incrementally add appropriate permissions to your app as you iterate your code.

If you want to allow all access, you can put -A into permissions setting, allow all access and sort out permissions later (well, that’s what you’ll say but you know it’ll slip and you’ll do a production deployment with it still in place). It's not recommended but is usefule to know.

The arguments are added to the deno command when the container and its launch file are built.

Next is the unstable setting. There are APIs that are labelled unstable in Deno which are not available by default. By adding --unstable to the command line, applications can access these APIS. With the configuration file, setting unstable=true achieves the same effect for the buildpack

Finally, the deno_version setting can force the buildpack to use a particular Deno version. For example, if you needed to use Deno v1.0.2 for an application, then adding:


To the .config file would make the buildpack use v1.0.2. Don't forget to use the v in the version number.

Step by Step

Creating an app

So, let’s build a Deno app. We’re going to use dinatra, a Deno module which gives Sinatra-like capabilities to Deno apps. You’ll of course want to install Deno and create a directory for your app. Then make a server.ts file and put this in it:

import {
} from "";

const greeting = "<h1>Hello From Deno on Fly!</h1>";

get("/", () => greeting),
get("/:id", ({ params }) => greeting + `</br>and hello to ${}`),

And that’s an entire simple web server application. Don’t forget permissions though: create a .config file with permissions="--allow-net" in it; all this application wants is network access.

Extra steps

You may want to test your app before deploying it. You have two options.

The first is to just run it before packaging it into a container image.

Running the deno command with the permissions:

deno run --allow-net server.ts

will be all you need in that case

The second is to package up the container image and run that image. You’ll need Docker and's pack installed locally to do this. Use pack to create the image:

pack build test-server-app --builder flyio/builder

Then use Docker to run the test-server-app image:

docker run -p 8080:8080 test-server-app

Deploying to Fly

Now we can create a Fly app for this code by running

flyctl init --builder flyio/builder

And we can deploy it with flyctl deploy

What Next

For Fly Buildpacks

We’ve made all the Buildpack code available in a GitHub repository for anyone who wants to improve the process. We’ll be looking to add new buildpacks to it too, and enhance existing buildpacks. It’s worth noting that you can build your own local buildpack and use it with the fly-builder stack for local/test builds - you’ll have to publish it with public access if you want to do Fly deployments with it though.

For Deno on Fly

We’re ready for your next Deno app on Fly, even if it’s your first. With a simple build and deploy process, it's easier than ever.

Update: 8/6/2020: Both the Deno buildpack and this article have been updated with a streamlined configuration mechanism.