This is the fourth article in our Azure series. So far we have looked at deploying a simple Node.js application to the Azure platform, using Pino and Event Hubs to collect logs, and using Stream Analytics to gain real-time business intelligence.
In this post we are taking advantage of the new Web App for Containers service offered by Azure to deploy a containerized web application to the Azure cloud. We also take a look at deploying our own container registry on Azure and using webhooks to link up a basic continuous deployment pipeline. The figure below shows the deployment architecture we are aiming for.
A containerized deployment architecture using Microsoft Azure Web App for Containers.
Note: We go into more detail about getting started with Azure in the first post in this series. If you have worked through that post, you will have already laid a lot of the groundwork for what follows. Otherwise, you will just need to follow these instructions.
To start with, you need a Microsoft account to access Azure. Your company administrator can create a login for you, but if you just want to just try out Azure you can sign up as an individual for a free trial at azure.microsoft.com/en-us/free/.
With an account, you can set up, view and configure Azure products either via the web-based portal or via Azure CLI. We use the Azure CLI for this exercise. CLI installation instructions are available for macOS, Linux and Windows.
The Azure portal allows you to interact with, and create, Azure resources through a graphical user interface (GUI).
Finally, we need an application to deploy. In the first post of this series, we discussed a sample application architecture using Hapi and Typescript, and we continue to use this as our canary application. You can drop in any Node.js or other application you like. Grab a copy of the sample application using git by running the following command:
git clone email@example.com:nearform/azure-typescript-hapi.git
A Proof-Of-Concept Deployment
Before we do a deep dive, lets walk through a proof-of-concept deployment of a basic NGINX web server Docker image from Docker Hub. This lets us get used to the setup, whilst isolating any potential problems before trying a more complex setup.
Log in to the Azure CLI using the following command, substituting your own login details:
az login -u <username> -p <password>
Setting up a deployment
Before you deploy an application, there are various organisational and administrative constructs to create. Compared with some other cloud providers this does feel a bit like additional overhead, but there is a case to be made for making explicit setup choices. Undoubtedly the structure is beneficial for more complex deployments. Fortunately, if you have followed through previous blog posts, or already work with Azure you already have most of these in place.
1. Create a Deployment User
This is separate to your Microsoft account and can be shared with members of your team, CI bots and so on. Check if you already have one registered using the CLI with the following command:
az webapp deployment user show
Otherwise, create one by running:
az webapp deployment user set --user-name <username> --password <password>
2. Create a Resource Group
Create (or choose, if you already have one) a resource group. A resource group is a logical grouping of resources associated with a geographical region. You can find more information on the significance of resource groups here. Choose a name and location for your resource group (in this example we use West Europe).
List existing resource groups with the following command:
az group list
If there is not one already created, or you want to create a new one, run the following command:
az group create --name <resource-group-name> --location "West Europe"
Tip: You can also register your resource group (as well as other settings encountered later) as the default resource group in the CLI using:
az configure --defaults group=<resource-group-name>
This saves you having to specify the group for lots of CLI commands. In our examples below we do not assume any defaults are set and we explicitly specify all settings.
3. Create a Service Plan for your Resource Group
Create (or re-use) a service plan associated with this resource group. Service plans define the size and features, and therefore cost, of the server farm provisioned. In this example we use a Standard level plan (
--sku S1). Create a service plan by running the following command:
az appservice plan create --name <service-plan-name> --resource-group <resource-group-name> --sku S1 --is-linux
Note: Here we use the
is-linux flag. We must have a Linux service plan to use containers and so we have to use at least a Basic level service plan.
Deploy a Containerized Application
Now that all of the infrastructure is in place, lets go ahead and deploy our application. Here we are going to use a simple hello world Docker image from Kitematic (kitematic/hello-world-nginx) which uses NGINX to serve a static site.
Run the following command to deploy your application:
az webapp create --resource-group <resource-group-name> --plan <service-plan-name> --name <app-name> --deployment-container-image-name kitematic/hello-world-nginx
Now run the
browse command and the CLI opens a browser window showing our freshly minted application.
az webapp browse --resource-group <resource-group-name> --name <app-name>
You should see something like the following:
Tip: If you do not see the kitematic hello world page, try checking the logs for your webapp. Use the following command to view logs in your terminal:
az webapp log tail --name <app-name> --resource-group <resource-group-name>
Congratulations! We have successfully deployed a containerized app to Azure Web Apps.
Whilst this demonstrates a proof of concept, it is likely that you will have at least some container images that you do not want to host publicly on Docker Hub. Fortunately, Azure integrates with any Docker container registry. This allows you to choose where you store your container images, for example in a private cloud or on a dedicated CI/CD server. In the next section we take a look at setting up our own private container registry using Azure Container Registry.
Creating a registry with Azure Container Registry
It is very quick to set up a private Docker registry with Azure Container Registry. We use the service level Managed_Basic as we want to use the webhook support this provides. To create a registry, choose a name and a resource group to associate it with. You can use the same resource group that we used in the previous section. A registry requires a storage account, but one will be created when you create a registry if you do not already have one.
Run the following command to create a registry:
az acr create --name <container-registry-name> --resource-group <resource-group-name> --sku Managed_Basic
Azure automatically creates an admin account in a disabled state for your repository. Run the following command to enable it:
az acr update -n <container-registry-name> --admin-enabled true
You can now access registry credentials with the command:
az acr credential show -n <container-registry-name>
Now that your registry is set up, check its contents with the following command:
az acr repository list -n <container-registry-name>
At this stage you see an empty list, as we have not yet put any images into our registry. Our next task is to create a Docker image, and push it up to our registry, ready to deploy.
Setting up a Containerized Application
We use the same Node.js app that we used in the first post in this series, but here we will be using the
docker branch. Grab a copy of the repository and checkout the
docker branch by running:
git clone firstname.lastname@example.org:nearform/azure-typescript-hapi.git git checkout docker
The main difference versus previous posts is that we have introduced a Dockerfile. We wont go into too much detail about how Docker works here, as it is out of scope. If you want to use your own Node.js application, and do not currently have a Dockerfile set up, here is a great how-to guide from the Node.js foundation. To make images you will need to have Docker running (you can find installation instructions here).
1. Log in to the Container Repository
First you will need to log in to the container registry you set up in the previous section. The Azure CLI provides a convenience command for logging into Azure Container Registry registries for you. Simply run:
az acr login -n <container-registry-name>
2. Make an Image of the Application
Make an image of the application to store in our repository. To do this
cd to the application directory, and run the command:
docker build -t <image-name> .
This builds an image of the current directory using the applications Dockerfile, and tags it as image name provided. You can check that your image has built successfully by running the following command:
docker run -it --rm -p 3000:3000 <image-name>
You should see your application running on
localhost:3000. In the terminal window use
Ctrl-C to terminate your application.
Note: If you are not using the sample application, you will need to change the port number (3000) to whichever port your application exposes.
3. Tag the Image
Tag this newly created image to associate it with our new remote repository on Azure. Use the following command:
docker tag <image-name> <container-registry-name>.azurecr.io/<image-name>
Note: Azure Container Registry supports multi level namespaces. We could equally tag our image with
<container-registry-name>.azurecr.io/my/new/image. This allows more complex hierarchical organisation of images.
4. Push the Image to the Remote Repository
Run the following command to push the image to the remote repository (this may take a while on slower connections):
docker push <container-registry-name>.azurecr.io/<image-name>
Check it is stored in our registry by running the command:
az acr repository list -n <container-registry-name>
Deploying Our Application
At this stage we have created a containerized app, and successfully uploaded it to our newly created container registry. The last thing to do is to deploy our app to our Azure Web App instance (which we created earlier). To do this we must change a few of the settings for our Web App instance to let it know where to find our new container registry.
Note: From here we will switch to using the abbreviated options
-g for resource group and
-n for app name for brevity.
1. Change Container Registry Settings
Change the container registry settings for the Web App instance with the following command:
az webapp config container set -g <resource-group-name> -n <app-name> \\ --docker-registry-server-url https://<container-registry-name>.azurecr.io \\ --docker-custom-image-name <container-registry-name>.azurecr.io/<image-name>
Note: Here Azure automatically includes your registry credentials because we are using an Azure registry. If you are using a non-Azure private registry, you will also need to set your credentials with the
Tip: If you create a new web app at this stage, you will need to create it with dummy container data (via the
--runtime options) before you set up a custom container registry. This seems like an oversight in the CLI design. We raised this with Microsoft and they told us that theyre currently working on improving the CLI in this area, as well as better documentation around this feature, so watch this space!
2. Assign a Port
We tell our Web App instance which port our application exposes, so that this port is exposed to external traffic by Docker. For our sample app, this is port 3000. Run the following command:
az webapp config appsettings set -g <resource-group-name> -n <app-name> --settings WEBSITES_PORT=3000
Now with the settings changed, restart your application using the command:
az webapp restart -g <resource-group-name> -n <app-name>
Check out your newly deployed app with the command:
az webapp browse -g <resource-group-name> -n <app-name>
Et Voilà, your newly deployed container is available in your browser.
Tip: If your browser is showing an error message at this point, try running your Docker image on your local machine. Pull it from your repository with the command:
docker run -it --rm -p 3000:3000 <container-repository-name>.azurecr.io/<image-name>
You can also inspect logs for your application with the command:
az webapp log tail --name <app-name> --resource-group <resource-group-name>
To recap, we carried out the following tasks:
- Set up a private container repository with Azure Container Registry,
- Pushed our own container images to it,
- Used those images to deploy our own Azure Web App.
So far so good! However, most projects these days look to deploy on an increasingly frequent basis, and it would be great to be able to automate this. Thankfully Azure supports various continuous deployment options. In the next section we will look at one in particular: using webhooks with our Azure Container Repository to automate new deployments of our application.
Continuous Deployment with Azure Container Registry
To set up continuous deployment from our registry, we simply need to set up a push webhook to our Web App instance to let it know a new image is available. The first thing we do is enable the continuous deployment setting for our Web App instance with the command:
az webapp deployment container config --enable-cd true -g <resource-group-name> -n <app-name>
The return value from the above command gives a setting with the name
CI_CD_URL which contains a long URI looking something like:
This is the inbound URI for webhooks and we need it to set up the webhook from our registry. You can always re-fetch this URI later with the command:
az webapp deployment container show-cd-url -g <resource-group-name> -n <app-name>
With this URI go ahead and create a webhook from your registry using the following command.
Tip: Wrap your URI in single quotes as they tend to include a $ character.
az acr webhook create -n <webhook-name> -r <container-registry-name> --uri <cd-uri> --actions push
Note: If you try and create more webhooks than your plan allows, you might see an unhelpful error message along the lines of
'unicode' object has no attribute 'get'. Running the command again with the
--debug flag gives more information on the underlying problem.
The above command sets up a push action webhook, which fires when you push new images to your registry. You can read more on Docker webhooks here. Now that you have configured a webhook, any changes you push up to your registry are automatically released to your web app; we have a basic CD pipeline in place. Go ahead and test this by making some changes to your original Node.js app, and then rebuilding and pushing the image using the commands:
docker build -t <image-name> . docker tag <image-name> <container-registry-name>.azurecr.io/<image-name> docker push <container-registry-name>.azurecr.io/<image-name>
Your updated app is deployed automatically.
Tip: If you dont see changes deploying automatically, you can check your webhook is firing, and inspect request and response objects, in the Azure portal.
We have reached the end of our example. With any luck you will now have a containerized application of your choice deploying automatically to Azure. In order to tear down the infrastructure we have used for this example, we can simply delete the resource group that contains it. This will delete all resources associated with that group, and delete any service plans that now have no linked resources. Delete a resource group by running the following command:
az group delete --name <resource-group-name>
Note: If you are using an existing resource group, you can just delete the specific resources we have created via the Azure portal.
We have successfully deployed a containerized application using Azure Web Apps for Containers, and set up a simple continuous deployment pipeline for that application. From this platform we can go on to make use of the other built-in features of Azure Web Apps, such as on-demand vertical and horizontal scaling, and performance testing. However these go beyond the scope of this post.
We have covered the basics of the Azure CLI commands for interacting with web application containers, and container registries. Full reference documentation is available here. Although we have chosen to use the CLI for this example, we could equally have set up this architecture using the GUI available at the Azure portal.
Supporting custom container deployments is a big step forward for the Azure Web App product. For many projects it represents the sweet spot between a typical platform-as-a-service offering and wrestling with full blown container orchestration.
As the service has only recently achieved general availability, there are a few rough edges with the documentation and CLI. However our interactions with the Microsoft team make it clear theyre working hard to squash bugs and refine an already top-notch developer experience with Web Apps for Containers.
Finally it’s fantastic to see Microsofts continued commitment to open source software in general and Docker in particular.