This morning my twitter feed featured a post that mentioned Deno
.
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
andRegistry 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 fromabc
.abc
is a Deno framework to create web applications. More info here.- the application will be started at port
80
. I chose80
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 fromEXPOSE 80
informs Docker that the container is listening on port 80 at runtime.- the
CMD
directive references themain.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 aContainer name
- Select
Azure Container Registry
as yourImage source
- Select the correct
Registry
andImage
- for now, just use
latest
asImage 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.