A Simple Docker Deployment

In this video we’ll kick the tires on Azure and see if it will work as the production home of project I’ll be working on. I don’t have an application just yet, but one is in the planning.

I want to start by doing the simplest thing I can think of: deploying the sample code from the Node “Getting Started” guide. I don’t want to deal with dependencies, build processes, or unit tests just yet. I want to do the least possible thing so I can isolate any problems I run into.

Resources

Transcript

Step 1: Setting Up The App

Here’s the app I’ll work with, the canonical “Hello World” web application from the NodeJS website. I’ll copy this code directly into VS Code without changing a single thing.

Responsive image

Next, I’ll run it to be sure everything works:

Responsive image

Not much of a surprise here, it works as expected:

Responsive image

Step 2: The Tools

My friend K. Scott Allen suggested that developers think of Azure as a “programmable data center”, which I quite like. I’m a big fan of working with command line interfaces and, if I’m being honest, the Azure portal can be a little difficult to figure out at times.

For this video I’ll be leaning completely on the command line interface, but rather than rattle off commands from memory, I’ll show you the process I used to discover how to use this powerful tool.

The first step is to be sure you have the CLI tools installed. You can download them online easily.

Responsive image

The next step is to set your default subscription. Most people I know only have a single Azure subscription, but it’s possible to have more than one tied to your Azure account. If you do, then be sure to set your default subscription using this command.

Responsive image

Finally, I’ll be working on Docker today. If you’re new to Docker, don’t worry as I won’t be doing too many crazy things. However you will need it installed if you plan on following along.

Responsive image

Step 3: Containerizing Our Application

If you’ve never worked with Docker, this might look a bit weird. I would encourage you to pause the video and watch a quick introduction on YouTube if you need.

The first thing that I typically do when starting with a web application is to create a Dockerfile and also a Makefile. You don’t need to use Make, I like it simply because it helps with repetitive tasks - like entering Docker commands.

Responsive image

I’ll fill out the Dockerfile using the simplest possible commands, pulling the image for NodeJS, copying my code file into the container, exposing port 3000 which is the port under which my application will run, and finally creating the default entry command.

Responsive image

It’s important to me that I have the least possible amount of code and orchestration at this point because I want to get the “feel” of Azure as clearly as I can.

I’m a big fan of using shell scripts, but typing them is something I’m not terribly fond of. That’s why I like to use Make for common tasks that need to happen in a particular order. I’ll add two targets: build and run:

Responsive image

I’m going to name my application “velzy”, after the great Dale Velzy, one of the pioneers of surfing that used to live right down the road from me.

I’ll build the application by executing make build:

Responsive image

Once that’s done, I’ll run it with make run:

Responsive image

Let’s see if that worked! Oops:

Responsive image

This is a very common issue when you move your code into a Docker container: understanding the difference between ports on the virtual machine running your containers and the host machine, running Docker.

The problem I’m having is that Node is binding my app to 127.0.0.1 over port 80. This, unfortunately, is not translating correctly to the host machine. To fix this, I need to bind to all IP addresses within the virtual machine. To do that, I simply need to change my code to specify the host as 0.0.0.0:

Responsive image

Great. Now I just need to stop my container, rebuild the image, and then run it once again. It works!

Responsive image

The final step is to publish the container to a public repository. I’ll use DockerHub for this for now, just to test things out. Later on, I’ll want to be sure this image is private, either at DockerHub or using a custom registry. We’ll deal with that in a later episode.

Responsive image

Step 4: Figuring Out the CLI

I have the Azure command line interface - the CLI - installed and I now get to figure out how to use it. If you’re used to visual tooling, the text-based programming interface is going to seem a little weird.

So let’s start with the simplest thing: we’ll execute the command az and see what Azure has to say to us:

Responsive image

Most CLIs will list off all of the commands that you can execute, as well as a description. If the CLI is small you’ll also typically see various options and flags that you can set. There are a lot of commands here, but the key is to not get overwhelmed, which is easy to do. I’m trying to find out how to create a web application so let’s look for the word “web” - and here it is right at the bottom:

Responsive image

It would be nice if the description was a bit more detailed, but for now it looks like this is what I need. Let’s run the command az webapp and see what happens:

Responsive image

We get an error message telling us that a subcommand was required. This is how the Azure CLI is constructed: there are parent commands that each have a series of subcommands to them. Reading the list here, we can see that just about every task we can think of is covered with regards to web applications.

We can get into those later. Right now I want to create a web application so I’ll use az webapp create and follow the CLI’s suggestion of using -h (a standard way of asking for help with a CLI):

Responsive image

The result is very useful. I can see at the very bottom what a successful command might look like. The options, however, don’t mention anything about deploying a container. Scrolling up… and I can see two things that are very useful:

Responsive image

The first is that an application name is required, which makes sense. I also need a “plan”, which appears to be an “app service plan” according to the details. The final thing I need is to create a resource group. If this is my first time working with Azure, I have no idea what any of these things are.

At this point I could quickly search the internet for answers to these questions, but I’d rather see how far I can go with the CLI before I get lost down any rabbit holes.

Step 5: Create a Resource Group

The things you create inside of Azure are called “resources”, so it seems pretty straightforward that a resource group is a way to keep things tidy within Azure. We can find out how to create one by executing az one more time and looking for a command with the words “resource” or “group”. Scanning down the list, I see the group command right here, and it appears to be what I need:

Responsive image

I’ll ask the CLI for help with the group command, and it shows me a list of all of the subcommands and what they do:

Responsive image

It looks like create will be what I need, let’s see what’s required:

Responsive image

I need to give az group create two bits of information: a name, which makes sense and also which Azure data center this group, and all its resources, will live in. The details also tell me to run az account list-locations to find out the possible names I can choose from:

Responsive image

The result is a pretty big splash of JSON and there are ways to make this read a bit better - but I’ll deal with that at a later time. Scanning over this list is pretty easy, and I’ll pick West US 2 as my data center.

I’ll call the resource group “velzy”, after the name of my application - and with that I’m ready to create my new resource group:

Responsive image

I once again see a JSON response from Azure telling me that my resource has been provisioned. First job is done!

Step 6: Create an AppService Plan

The az webapp create -h help output said that a --plan argument was required. The detail of that mentioned that we could create one with az appservice plan create but that’s about it.

The obvious question here is: what the heck is an App Service Plan? This can be a little confusing, because there are resources in Azure called “App Services” and there are other resources called “App Service Plans”. Reading through the documentation can add to the confusion, unfortunately. For the answer, I turned to my colleague Scott Hanselman, who told me that I could think of an App Service Plan as the VM my app will be running on - even though an App Service Plan has nothing to do with VMs, they are conceptually equivalent.

An App Service plan is measured in terms of CPU cores, RAM and storage capabilities, much like a virtual machine might be. You can search the internet for “App Service Plan Pricing” and you’ll end up at the table below:

Responsive image

One other thing to note about App Service Plans is that they can be reused for multiple resources. I could deploy 10 web applications to a single App Service Plan, or create multiple instances of a given database. Now that we know all of this, let’s create our plan. I’ll add a -h flag to the az plan create command so I can understand what I need to do next.

The only required arguments for this command are a resource group name as well as the name for our plan. I can also add a few other options, which in my case are very important.

The first is the --is-linux flag. If I don’t set this, I’ll get a Windows App Service Plan, which won’t work with deploying docker containers. I’ve learned this the hard way:

Responsive image

The next argument I want to pay attention to is --sku because this will dictate how much I’ll be paying each month. Pricing varies due to a number of factors, including data center and subscription plan. Head over to the App Service Plan Pricing page to see which plan works for you.

I’ve decided that a Standard Plan, or S1, will do nicely for this application. I can see the list of possible SKUs by scrolling up and reading the details:

Responsive image

Note that a free plan won’t work if you’re planning on deploying a Docker container, as I am. Now that I have all of that, I’m ready to create my App Service Plan:

Responsive image

Step 7: Create a Web App for Containers

We’re almost there! I have my resource group and my app service plan, now all I need to do is to come up with a name for my application and set the deployment image.

If you recall, I’ve deployed the simplest Node app I could come up with to DockerHub under the tag robconery/velzy. That’s the last piece of this puzzle: how do I tell Azure where to get the image?

If you recall, executing the command az webapp create -h lists out all of the arguments that you can send in, including one for deploying a Docker container, which I can specify using -i:

Responsive image

With that, my command is completed! I simply need to use the command:

az webapp create -g velzy \
                 -p velzyplan \
                 -n velzyapp
                 -i robconery/velzy

The -g flag specifies my resource group, the -p flag specifies the App Service Plan. The -n flag is the name of my app and -i is the DockerHub image tag. This a pretty simple naming scheme and your needs may vary; go with whatever works for you and your organization.

As usual, we get a splash of JSON back. If you get an error, read the messages carefully as they are typically quite detailed. My most common error is usually due to spelling.

There’s one bit of JSON here that I care about most, the URL to my new site:

Responsive image

Let’s go have a look!

Step 8: Wait

Unfortunately the startup process isn’t very fast. All we’ve done up to this point is to tell Azure all of the settings that go into running this web application - we haven’t actually told Azure to run it. The good news is that we don’t have to do that explicitly, we can just visit the URL:

Responsive image

This experience is underwhelming as it gives no sense of what’s happening. The spin up time for a containerized web application can be as short as 30 seconds or as long as 10 minutes, depending on numerous factors. Each resource that will power this web application needs to be started up and, once that’s done, the image needs to be pulled from DockerHub and a container started. This can take a while!

To better understand what’s going on, let’s head to the Azure Portal and turn on Diagnostics and Logging. You can do this through the CLI as well, but right now this is the easiest thing to do for us. We’ll head over to our velzyapp resource and scroll down, clicking on “Diagnostic logs”, which is turned off by default. We’ll select “File System” and click save. Now, let’s click on “Log stream”. This will tail the logs for us so we can see, realtime, what’s happening in the background. As you can see - not a lot right now.

While we’re waiting for logs to appear, let’s have a look at a very common problem that bites a lot of people when they first try to deploy a containerized web application to Azure. I’ve had this happen to me more than I care to say. If your application uses a port other than the default web port (80), you have to make sure you set the WEBSITES_PORT application setting. You can do this in the Application Settings blade on the portal:

Responsive image

The other thing I could have (and probably should have) done is to use the PORT environment variable in my code:

Responsive image

This is such a common stumbling block for Node developers, however, that the Azure App Service team decided to handle port 3000 by default.

OK, about 3 minutes have gone by, let’s see if our logs are telling us anything:

Responsive image

The output shows that Azure is pulling our image from DockerHub and is attempting to start it. Waiting for another 30 seconds and we see something that isn’t terribly exciting, a 502 server error:

Responsive image

This can be confusing, especially for .NET developers who recognize this as an ASP.NET error page. Our App Service is supposed to be Linux! What’s this page doing there?

The quick answer is that this is the server that sits in front of our container and routes traffic to it, which runs on Windows and uses Microsoft IIS. It’s telling us that it can’t reach our application, and it expected to. When this happens after a long wait it typically means one of two things:

  1. Our container hasn’t started/wasn’t able to start due to an error
  2. Our ports aren’t setup correctly

The first thing I typically do when this happens is to wait for another minute or two and then refresh the page. If that doesn’t work, I’ll go through and make sure that all my ports are correctly set. In this case we get lucky and refreshing the page does the trick:

Responsive image

The URL indicates that the page is not secure, which we can change manually by using https:

Responsive image

Great!

Epilog

When we’re all done playing around, we can make sure that don’t have to pay for resources we’re not using. This is one of the primary benefits of using resource groups - we can delete everything we’ve created with a simple command:

az group delete -n velzy

We’ll be asked to confirm our choice, and then we’re all done! That’s it for this first AzureCast. I hope you enjoyed it!