Deno on Fly using Buildpacks
Tags: deno, buildpacks, cloudnative
Published: 2020-05-05 15:09:41

We really like [Deno](https://deno.land) at [Fly.io](https://fly.io). It really is a better Node and we’d love people to build more with it. Our plan to aid in the adoption of Deno? “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 [deno.land](https://deno.land/) 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 apps create --builder flyio/builder`
- `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](https://fly.io/blog/powerbuilding-with-fly/) landed in early April and is based on the Cloud Native [Buildpacks.io](https://buildpacks.io/) (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.

**Stacks**

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.

**Builders**

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 Buildpacks.io 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.

**Buildpacks**

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

***detect***

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.

***build***

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.

***permissions***

Now, one thing Deno does is restrict access to resources by default. You have to specify which resources are available to any program with `--``allow-*` command-line flags. Of course this is hard to pass through the command chain, so we don’t. What we do is add a `.permissions` file, containing the command-line arguments you’d expect to be added to the command-line, to the directory with the code we’re building. In the example app, this file contains:

```
--allow-env --allow-net
```

Allowing environment variable and network access. If there’s no `.permissions` file, currently we default to no permission flags, in sync 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`, 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.

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


## Step by Step

**Creating an app**

So, let’s build a Deno app. We’re going to use [dinatra](https://github.com/syumai/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 {
    app,
    get,
    post,
    redirect,
    contentType,
  } from "https://denopkg.com/syumai/dinatra@0.11.0/mod.ts";
  
  app(
    get("/hello", () => `Hello`),
    get("/hello/:id", ({ params }) => `Hello to ${params.id}`)
  );
```

And that’s an entire simple web server application. Don’t forget permissions though: create a `.permissions` file with `--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 --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](https://www.docker.com/products/docker-desktop) and Buildpacks.io's [pack](https://buildpacks.io/docs/install-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 apps create --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](https://github.com/superfly/fly-builders) 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.

**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.