The Zero To Mastery Deno Course deploys a Deno docker container to Amazon EC2.

The EC2 instances cost money after you’ve exhausted your free 12 months. Plus, Amazon’s cloud services can be tricky. Sometimes, services spike. You quickly exceed your free tier, even for a simple toy app.

Let’s deploy Deno to Heroku instead. Heroku’s free tier stays free regardless of usage.
The free level is slow, because it will periodically stop to save resources. But for a toy app that’s not a problem.

Prerequisites

You’ll need a free Heroku account and the following programs on your local machine:

Fork and Clone the Starting Repository

Find the code for the Zero to Mastery Deno course. I have made a fork that I will use for this guide.

Make your own fork and clone it to your computer or just download the original source code.

The repository already contains a Dockerfile which we can use.

Prepare for Heroku

Heroku’s web dynos dynamically bind to a port variable. You have no control over which port your application will receive.

Our current implementation hard-codes the web port to 8000. That won’t fly with Heroku.

Thus, we have to make changes to our code.

(You can see all changes on GitHub.)

Adjust Dockerfile

Delete the last line: EXPOSE 8000.

Change Drakefile.ts

Remove the hard-coded port and add a port as a flag:

desc("Run API");
task("start", [], async function () {
  await sh(
-    "PORT=8000 deno run --allow-env --allow-net --allow-read src/mod.ts",
+    "deno run --allow-env --allow-net --allow-read src/mod.ts --port=${PORT}",
  );
});

Read flags

We need the flags module from the standard library.

Inside src/deps.ts:

  // Standard library dependencies
  export * as log from "https://deno.land/std@0.57.0/log/mod.ts";
+ export * as flags from "https://deno.land/std@0.57.0/flags/mod.ts"

The main program needs to read the port from the command flags (src/mod.ts) 1.

- import { log, Application, send } from "./deps.ts";
+ import { flags, log, Application, send } from "./deps.ts";

import api from "./api.ts";

const app = new Application();

const PORT = 8000;
+ const argPort = flags.parse(Deno.args).port;
+ const port = argPort ? Number(argPort) : PORT;


+ if (isNaN(port)) {
+   log.error("Port is not a number.");
+   Deno.exit(1);
+ }

// more code

if (import.meta.main) {
-  log.info(`Starting server on port ${PORT}...`);
+  log.info(`Starting server on port ${port}...`);
  await app.listen({
-    port: PORT,
+    port: port,
  });
}

You can see all the changes on GitHub.

Heroku Setup

Open the terminal and log in:

heroku login

Create a new app:

heroku create

Build the Docker image and tag it with the Heroku registry format:

registry.heroku.com/<app>/web

For example,

docker build -t registry.heroku.com/red-goose-58626/web .

(Remember to use your app name.)

You can test your build locally:

docker run -it --rm -e PORT=8080 -p 8080:8080 registry.heroku.com/red-goose-58626/web:latest

(CTRL+C will stop the docker container.)

Your Deno app should start and be available at http://localhost:8080.

Push the image to the Heroku Docker registry:

docker push registry.heroku.com/red-goose-58626/web:latest

(Remember to use your app name.)

Deploy the docker image to Heroku now:

heroku container:release web

You can now reach your Deno application on the web via the Heroku URL, for example, https://red-goose-58626.herokuapp.com/index.html

Final Thoughts

Deploying a Deno Docker container is trivial after you’ve tackled the dynamic port binding.

I’ve used several resources for creating this blog post (see below). Credit goes to the original authors.


  1. I copied the implementation from the Heroku buildpack for Deno. ↩︎