Running a Containerized Deno Web Application on Microsoft Azure Container Registry

May 14, 2020 - Steven Roeland

This morning my twitter feed featured a post that mentioned Deno.

@JessTelford

Not really knowing what the fuss was all about I started doing some research and eventually I ended up with a full web application running containerized on Microsoft Azure using Container Instances. What a beautiful day indeed. In this post I will give you a step-by-step overview of how I got to that point and what the challenges and hiccups were along the road.

But first, let's take a look at what Deno really is. From the official website:

Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.

  • Secure by default. No file, network, or environment access, unless explicitly enabled.
  • Supports TypeScript out of the box.
  • Ships only a single executable file.
  • Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).
  • Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std

The Deno project was created by Ryan Dahl, original creator of Node.js. I would strongly encourage you to watch 2 talks from him on YouTube which make a lot of things clear: 10 Things I Regret About Node.js and Deno, a new way to JavaScript.

Just to make sure, this article will not be a node vs deno discussion. This article will show you how to serve an application using containerized Deno. That being said, buckle up, let's get started.

Getting your application up and running will come down to this:

  • Create an Azure account if you don't have one yet
  • Create an Azure Container Registry
  • Install docker desktop + Deno
  • Build the Deno docker image
  • Push the Deno docker image to your Container Registry
  • Create a Container Instance off your docker image
  • See if everything works and cheer if it does

Setup an Azure account if you don't have one yet

Having worked a lot with Google Cloud Platform(GCP)/Kubernetes on my last project, I chose Azure to host my docker images this time to see what they had done related to containers since the last time I used it. Getting started with Azure is really easy. Just head over to the Microsoft Azure Website and create a new account. You can start off for free and even get free credit for the first month.

Of course, you can choose whichever platform you like to host your docker containers. For the sake of this demo, I will include the steps to configure this on Azure.

Create an Azure Container Registry

Once you have your Azure account all set up, on the portal

  • search for Container Registries
  • select Add in the top left corner
  • provide a Resource group and Registry name
  • Next through the remaining steps of the wizard to Create a new registry

Once your registry is created, head over to the Access keys section in the Settings section of your registry. Enable the Admin user toggle. This will allow us to connect to the repository using docker login later on.

Install docker desktop + Deno

Head over to the official Docker Website and download the correct version of Docker Desktop for your machine. This article will not cover docker itself. I advise you to go through the excellent docs on the docker website to get familiar with the main concepts if you aren't already.

When building a Deno application, it might also be nice to install.. Deno.

Using PowerShell

iwr https://deno.land/x/install/install.ps1 -useb | iex

This will allow you to run your Deno application without having to actually docker build and docker run.

Build the Deno docker image

Aha! Now that we have all of that out of the way, let's build the actual web application. For now, it seems as if there are no official Docker images YET (I will update the article accordingly when official Deno Docker containers appear online). Via one of the github issues on the Deno repo I came across this nice deno-docker project which I used as a starting point.

Create a new project folder somewhere on your filesystem. At a bare minimum, you will need the following 3 files:

A static html page to serve (index.html)

Let's start with the html file. Create a subfolder named public in your project folder and add an index.html file. You can go as crazy as you want with the content, that really is outside of the scope of this article.

An entry point for your application (main.ts)

Create a file called main.ts at the root of your project folder with the following content:

import { Application } from 'https://deno.land/x/abc/mod.ts';

const PORT = 80;
const app = new Application();

app
  .static('/css', 'public/assets/css')
  .file('/', 'public/index.html')
  .start({ port: PORT });

console.log(`Server started on port ${PORT}`);

Let's take a second to see what's going on here.

  • Application is imported from abc. abc is a Deno framework to create web applications. More info here.
  • the application will be started at port 80. I chose 80 specifically as this plays along nice with (the limitation of) Azure Container Instances. More on that, further on.
  • app.static() static registers a new route to serve files from the provided root directory
  • app.file() registers a new route with path to serve a static file

A Dockerfile to create your container image

Finally, we add the file that will allow us to create a nice Docker image of our web application. Add a file called Dockerfile to your root project folder (no extension). This is how it should look:

FROM hayd/alpine-deno:1.0.0

EXPOSE 80

WORKDIR /app

ADD . .
RUN deno cache main.ts

CMD ["run", "--allow-net", "--allow-read", "main.ts"]

Let's take another second to see what's going on HERE.

  • FROM hayd/alpine-deno:1.0.0 specifies the pre-existing image to start from
  • EXPOSE 80 informs Docker that the container is listening on port 80 at runtime.
  • the CMD directive references the main.ts file we created earlier

I would like to delve a little deeper into the CMD directive here. It describes how to run a container based on the image we are creating. The command that will be executed in a container will be:

deno run --allow-net --allow-read main.ts

With Deno, code is executed in a secure sandbox by default. Scripts cannot access the hard drive, open network connections, or make any other potentially malicious actions without permission.
Users must first give permission. Deno provides analogous behavior in the terminal.

--allow-net allows network access

--allow-read allows read access to the file system. This is necessary for our abc framework to serve our html file. If you don't specify the --allow-read flag, you will run into the following error at runtime:

{"statusCode":500,"error":"Internal Server Error","message":"read access to \"C:\\deno-web-app\", run again with the --allow-read flag"}

Since we're talking error messages.. One thing I ran into was that initially, the Dockerfile specified a user with limited permissions. This throws a very cryptic exception:

error: Uncaught PermissionDenied: Permission denied (os error 13)
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)
    at Object.listen ($deno$/ops/net.ts:51:10)
    at listen ($deno$/net.ts:152:22)
    at serve (https://deno.land/std@0.50.0/http/server.ts:261:20)
    at file:///app/main.ts:4:11

As it turns out, this was because we want to serve the application on port 80. Non-privileged user (not root) can't open a listening socket on ports below 1024. Thank you StackOverflow. To solve this, make sure that user running the process (which can be specified by using USER in your Dockerfile) has enough permission. In our case, just omitting the USER works just fine.

Now that we have everything in place. Let's build our Docker image.

docker build -t deno-web-app .

Push the Deno docker image to your Container Registry

Now that we've built that shiny Docker image, let's push it to our registry. To do that, of course, we have to follow a few more steps.

login to your Azure Container Registry

docker login [your-azure-registry-name].azurecr.io

You'll be prompted for a username and password. Use the credentials you configured in the beginning of this article while creating your registry.

After that, prefix the image with your registry login URI so that it can be pushed.

docker tag deno-web-app [your-azure-registry-name].azurecr.io/deno-web-app

And finally, push your image to your registry.

docker push [your-azure-registry-name].azurecr.io/deno-web-app

Create a Container Instance off your docker image

So now that we got the image in our registry, it's time to create an actual container, so that we can actually host our web application.

  • Go to Azure portal home page
  • In the search box type Container instances
  • select Add in the top left corner

This will bring up a step wizard where you need to provide some information about the Container Instance you're about to create.

On the first step

  • Provide a Resource group and a Container name
  • Select Azure Container Registry as your Image source
  • Select the correct Registry and Image
  • for now, just use latest as Image tag

On the next step - Networking - just enter a nice DNS name label, so that you will have a nice url to access your application. You can leave the default port settings.

When working with Docker containers, it is common to match the TCP port in the container to a port on the Docker host. One of the limitations I ran into with Azure Container Registries (as far as I can tell) is that this kind of port forwarding is not possible here. This is the reason why, in the Dockerfile I opted to open up port 80 in the container by using EXPOSE 80. That way it will do a PORT 80:80 binding between host and container.

Anyways, next through the remaining steps of the wizard to Create your Container instance.

See if everything works and cheer if it does

After a few moments, Azure should finish creating your Container Instance. You should now be able to see your web application running at the url you just configured. To find the exact url, look for the FQDN property of your Container Instance on its Overview page.

All good? Hooray!

tl;dr

The complete code for this article is available on our GitHub repo

The code on the repo slightly deviates from the code snippets used in this article. The index page of the web application on the repo has a templated landing page and therefore loads additional static content in the main.ts file. Also, the abc dependency was moved to a separate file.

This however changes nothing to the main building blocks discussed in this article and therefore, for the sake of simplicity, it was not mentioned here.

Final words

I really enjoyed learning about the Deno project. I really hope this article can be of value for anyone getting started with Deno, docker or Azure Container Registries. If you found this article useful, give us a quick shoutout on our new twitter account.

And with that, I would like to thank you for following along with me on my first REAL blog post.

I hope to welcome you here for another one.