How to deploy a dockerized app to Microsoft Azure Web App for Containers

Deploying a Docker container on Azure ‘Web App for Containers’ can be done fairly easy. In this blog post, I will provide a step by step guide to get you started. Some basic knowledge of Azure and Docker definitely helps. But why should you care in the first place? You will get:

  • a managed runtime (for a single image)
  • scaling to multiple instances
  • HTTPS
  • a simple deployment model
  • easy integration with App Insights (Azure’s Monitoring system for Web Apps)
  • use any Azure SaaS like CosmosDB, MSSQL, …

Overview

You won’t get a full blown orchestrated container runtime for your microservices architecture. For only a few services the setup can work. If not, migrating to another environment, later on, is straightforward, as we are using Docker. Below is an overview of the infrastructure we will set up.

Prerequisites

  • An Azure subscription
  • Local Docker installation

Steps

  • Create a Docker image
  • Create a Docker registry
  • Push the Docker image to the registry
  • Create a Web App
  • Push a new version of the Docker image

Create a docker image for our app

The only requirement for our Docker image is, that it must expose an HTTP service on port 80. For our example, we are going to create a Docker image that contains a tiny NodeJS app. Create a file called index.js and paste the following content:

var http = require('http');

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello docker!');
}).listen(80);

Those lines create an HTTP server, that returns a plain text response with the content ‘Hello docker!’ for any request. Now we create a Dockerfile. A Dockerfile describes how an image gets composed. Create a file named ‘Dockerfile’ with following the content:

# our base image is the official node image: https://hub.docker.com/r/library/node/
# alpine flavour is the smallest variant you can get
FROM node:8-alpine

# copy all files from our project dir into the image at location /app
COPY * /app/
# sets the current working directory for future commands to /app
WORKDIR /app

# declare that the container will provide a service on port 80
EXPOSE 80

# this will get executed when container starts
ENTRYPOINT node index.js

Now you can test your container:


# create an image tagged (read 'named') azure-docker-blog
docker build -t azure-docker-blog .

# create a container named azure-docker-blog from our image
docker run -d --rm --name azure-docker-blog -p 3000:80 azure-docker-blog

# check the service
curl http://localhost:3000/

# stop the container
docker stop azure-docker-blog

Create a container registry in Azure

Log in to https://portal.azure.com

All Resources > Add > Search ‘Container Registry’ > Create

  • Choose a name. In our case ‘azuredockerblogregistry’
  • Choose a name for a resource group. In our case ‘azure-docker-blog’

Wait for the success message in the upper right corner. Then click ‘Go to resource’.

Open ‘Access keys’ and activate ‘Admin user’. We need the following info from this page:

  • Login server (this is the server name of our image registry)
  • username / password (can be used to login into the registry)

Push an image to the registry

# create an image that is tagged for our registry (alternatively, you can tag the existing image)
docker build -t azuredockerblogregistry.azurecr.io/azure-docker-blog .

# login to the registry using the credentials from above
docker login azuredockerblogregistry.azurecr.io

# push the image
docker push azuredockerblogregistry.azurecr.io/azure-docker-blog

Create the Web App for Containers

On the Azure portal:

  • All Resources > Add > Search ‘Web App for Containers‘ > Create
  • Choose a name. In our case ‘azure-docker-blog-app’
  • Configure container
    • Image source: Azure Container Registry
    • Registry: azuredockerblogregistry
    • Image: azure-docker-blog, Tag: latest
  • Ok > Create

Wait for the success message in the upper right corner. Then click ‘Go to resource’. On the overview page for the App Service, you can find the public DNS name of the service. Open the address in your browser or use the command line (the first time it takes some seconds until it is ready):

curl https://azure-docker-blog-app.azurewebsites.net/

Make some changes

Now we will enable automatic pull of new images. On the portal site open the settings for your App Service and there enable ‘Continuous Deployment‘ under the section ‘Container settings’. Whenever we push a new version of our image, the App Service will now be notified and replace the existing version.

In order to deploy a new version of your app, you must create a new image. We add two more features to our server. First the output should contain the server start time and second we add a shutdown hook that gets triggered when the path ‘/shutdown’ gets called. Edit index.js

var http = require('http');

const serverStartTime = new Date();

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});

    if (req.url === '/shutdown') {
        res.end('Killing the process!');
        process.exit(1);
    } else {
        res.end('Hello docker! Server started: ' + serverStartTime);
    }
}).listen(80);

Build the image and push it again:

docker build -t azuredockerblogregistry.azurecr.io/azure-docker-blog .
docker push azuredockerblogregistry.azurecr.io/azure-docker-blog
Wait a few seconds for the new image to become active and check again
 curl https://azure-docker-blog-app.azurewebsites.net/ 

you should now see “Hello docker! Server started: …”.

With ‘curl https://azure-docker-blog-app.azurewebsites.net/shutdown‘ you can kill the NodeJS process. In this case, it will be restarted automatically.

Conclusion

We now have our app running and can push new versions by simply doing docker build + push. What runs inside the container is up to you. You are not restricted to NodeJS, but you can use anything that runs inside a Linux Docker container. Use your favourite JVM language or go with Haskell, if you like. Just expose your service via HTTP on port 80 and you’re ready to go.