Containers have become a widely-used DevOps technology. Containers give DevOps professionals the ability to define infrastructure as code, making it possible to create isolated environments for testing, deploying, and running applications. In this article, we will learn how to deploy a Node.js application using Azure containers to an Azure web app.

Prerequisites

To complete this tutorial, you will need:

  1. Node.js installed on your system (version >= 10.3)
  2. An Azure account
  3. A CircleCI account
  4. A GitHub account
  5. Azure CLI installed

With all these installed and set up, you can begin the tutorial.

Creating a container registry on Azure

Our first step is creating a container registry on Azure to store and build a Docker container. Go to your Azure portal home page and click Create. Then click Container Registry from the Containers menu.

Create Registry - Azure

On the registry creation page, fill in the information, including the name for your registry.

Create Registry Page - Azure

Click Review + Create to open the review page. Confirm your registry information, then click Create to trigger the registry creation process.

If you prefer, you can use the Azure CLI to create the registry by running:

az acr create --name fikayoregistry --resource-group Demos-Group --sku standard --admin-enabled true

I have named my registry fikayoregistry, but you might want to use a different name. If you do, be sure to substitute the name you choose instead.

Cloning the demo project

The next step is to clone the Node.js project to deploy to Azure using a Docker container. To clone the project, run:

git clone --single-branch --branch base-project https://github.com/coderonfleek/node-azure-docker.git

Next, use cd node-azure-docker go into the root of the project, then install dependencies using npm install.

This project is a basic Node.js API with a single endpoint for returning an array of todo objects. It also contains a test suite for testing the endpoint.

Run the application using the npm start command. The application will start at the address http://localhost:5000. Load the following route http://localhost:5000/todos in your browser to show the list of todos.

Todos - Local run

Creating and building a Docker image on Azure Container Registry

The next step is creating a Dockerfile so the custom Docker image can run the project. It will then be pushed to and built on the Azure Container Registry (ACR).

At the root of the project, create a new file, name it Dockerfile (no extension) and enter:

FROM node:current-alpine
COPY . /app
WORKDIR /app
RUN npm install
ENTRYPOINT ["npm", "start"]

This file uses a node image as its base with the tag current-alpine. It then copies the contents of the project into an app folder inside the image, sets app as the working directory and runs npm install to install dependencies. The application is booted up by running npm start.

At the root of the project, build the image using Dockerfile. Be sure to replace <container_registry_name> with the name you chose earlier. Run:

az acr build --registry <container_registry_name> --image mynodeimage .

Note the dot (.) at the end of the command. Make sure to include it; it defines the build context as the current directory.

This image has been given the name mynodeimage, which will appear in ACR. This command sends the folder’s contents to Azure Container Registry, which uses the instructions in the Dockerfile to build the image and store it.

To view the image you created, go to your Azure resources page in the portal. Click your registry then click Repositories from the Services section.

Docker Image - Azure Container Registry

Helpfully, every newly built image is tagged latest. The Docker image that contains the Node.js application is now available in your registry for deployment to the Azure App Service.

Enabling Docker access on ACR

To deploy the application, you need to enable Docker access in Azure Container Registry. This step allows the Azure App service to access the containers stored in the registry.

From your Resources page, click the registry to open its overview page. From the Settings section click Access keys.

Make sure that Admin User is enabled.

Enable Docker Access - Azure Container Registry

When the Admin User option is enabled, the button will turn blue and display a Username and some passwords. This information is what provides access to the image in the registry for the Azure web app.

Creating a web app

Next, create an Azure Web App. From the Azure portal, by click Home, then Create. Select Web App.

Enter the web app name and select:

  • Docker Container as the Publish option
  • Linux as the Operating System

Select a region, then click Next : Docker.

Docker Settings page - Azure

On the Docker page:

  • Select the Single Container option
  • Choose Azure Container Registry as the Image Source
  • Enter your registry name in the Registry field (I am usingfikayoregistry in this tutorial)
  • Use mynodeimage for the Image field
  • For Tag, select latest
  • Leave Startup Command empty; your Dockerfile already has an ENTRYPOINT command

Now we are ready to click Review + Create to review the details. Then click Create. After the web app has been created, use your browser to visit the address https://[YOUR_WEB_APP_NAME].azurewebsites.net/todos. Be sure to replace YOUR_WEB_APP_NAME with the name of the web app you created. I have used https://nodeondocker.azurewebsites.net/todos for mine.

App Live - Azure

After you have configured the web app, the Docker image is pulled and run. The first time your app loads is a “cold start”. That means your app will take some time before it loads. There is a “cold start” each time a fresh deployment is made from the Docker image. After that, the app is available immediately.

Enabling continuous deployment on the web app

You are not here for a one-time deployment, right? You want continuous deployment. To set it up in Azure, go to your Resources page and click your web app name to open the Overview page. From the Setting section on the side menu, by click Container Settings. On the Container Settings page, scroll down to the Continuous Deployment option, turn it on, and click Save.

Continuous Deployment - Web App

With continuous deployment selected, the web app will trigger a new deployment of the Node.js application each time the Docker image is rebuilt on Azure Container Registry .

Creating the CircleCI project

Before heading over to the CircleCI console, let me explain the strategy we will use to set up a continuous deployment pipeline to the web app using containers. You already know that the web app will redeploy a new version of the application when the image is built or rebuilt on Azure Container Registry. The goal is to somehow make sure that once code is pushed to a remote repository, a new build is triggered. These are the steps we will use in this tutorial:

  • Create a project on CircleCI with the GitHub repository
  • Create an Azure Container Registry task. This task watches a branch on the remote repository to rebuild the image anytime a push is made to that branch
  • Write a CI/CD pipeline script that pushes to the watched branch when tests have passed

We will begin by setting up the project on CircleCI.

First, run rm -rf .git at the root of the project to erase any existing git history. Next, push the project to a remote repository on GitHub. Make sure that this is the GitHub account connected to your CircleCI account.

Go to your remote repository and create a deploy branch.

deploy branch - GitHub

This branch will be the one that is watched to trigger rebuilds on the Docker image.

From the CircleCI dashboard, click Add Projects.

Add Project - CircleCI

Click Set Up Project.

Add Config - CircleCI

On the setup page, click Use Existing Config to instruct CircleCI that you will be adding a configuration file manually and not using the sample. You will be prompted to either download a configuration file for the pipeline or start building.

Build Prompt - CircleCI

Click Start Building.

Note: This build will fail because you have not set up the configuration file yet. We will do that later on in this tutorial.

Setting GitHub authentication on the CircleCI project

Because a push will be made to this project’s deploy branch from the pipeline configuration, we need to set up authenticated access so that CircleCI can access the repository on GitHub.

Fortunately, CircleCI provides a way to add a User API Key to achieve that. From your project, click Project Settings, (top right of the page) then SSH Keys (on the side menu).

In the User Key section, click Authorize with GitHub. If you have already done this, the option will not be visible, so skip the next paragraph and copy the fingerprint in the User Key block.

After the connection to GitHub is made, an Add User Key button appears in the User API Key section. Click this button to generate a fingerprint that we will use in the deployment pipeline, later on in the tutorial. Copy the fingerprint and keep it in a safe location.

Because the fingerprint will be used in the pipeline script along with your GitHub email and username, it is safer to put these credentials in environment variables. From the side menu on the Project Settings, click Environment Variables. Add these environment variables:

  • GITHUB_EMAIL: The email of your connected GitHub account
  • GITHUB_USERNAME: Your GitHub username
  • GITHUB_FINGERPRINT: The authentication fingerprint that was generated

Creating an Azure Container Repository task

I mentioned earlier that we need an Azure Container Repository task to watch the deploy branch for updates and trigger rebuilds. First, though, we need a GitHub personal access token.

From your GitHub page, click Settings, Developer Settings. From the side menu, select Personal access tokens. Then, click Generate new token. Confirm your password to get to the token creation page.

Enter a description for the key. Grant all permissions except delete_repo. For this tutorial, the most important permission is the ability to create a webhook in your repo. We will use this webhook to signal the Azure Container Registry to trigger a new build of the Docker image.

GitHub token - GitHub

Click Generate token and copy the token when it is displayed. The token will not be displayed again, so make sure you get the information now.

To create an Azure Container Registry task, go to the root of your project. Using your repository address, your registry name, and your GitHub access token, run:

az acr task create --registry <container_registry_name> --name buildnodeapp --image mynodeimage --context <your_github_repo>#deploy --file Dockerfile --git-access-token <access_token>

This code creates a buildnodeapp task that will rebuild the mynodeimage image each time updates are pushed to the deploy branch. To view your newly created task, go to the registry page and click Tasks.

ACR task - Azure

Writing the continuous deployment script

Our next step is to write the pipeline script for continuous deployment of the Node.js app to the Azure web app using Docker container. To enable the pipeline script to push updates to the deploy branch of the GitHub repo, we will use the node package gh-pages. Most often, this package is used to push files to a dedicated gh-pages branch to deploy static sites to GitHub pages. Conveniently, this package is configurable, so you can push files from one branch to another in any repository.

At the root of the project, install the gh-pages package as a development dependency:

npm install gh-pages --save-dev

Next, add the deploy script in the package.json file:

"scripts" : {
  ...
  "deploy" : "npx gh-pages -b deploy --message '[skip ci] Updates' -d ./"
}

This script invokes gh-pages using npx to push files from the main branch to the deploy branch. I added the --message parameter of [skip ci] Updates so that CircleCI does not re-run the pipeline when changes are pushed to this branch.

At the root of your project, create a folder named .circleci and within it, create a file named config.yml. Inside config.yml, enter:

version: 2
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: update-npm
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install-packages
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test

  deploy:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: Configure Github credentials
          command: |
            git config user.email $GITHUB_EMAIL
            git config user.name $GITHUB_USERNAME
      - add_ssh_keys:
          fingerprints:
            - $GITHUB_FINGERPRINT
      - run:
          name: Deploy to Azure Web App Using Azure Container Registry Task
          command: npm run deploy

workflows:
  version: 2
  build:
    jobs:
      - build
      - deploy:
          requires:
            - build # only deploy once build job has completed
          filters:
            branches:
              only: main # only deploy on the main branch

This file defines a workflow with two jobs: build and deploy.

The build job checks out the code from the remote repository, installs dependencies, and runs the tests to make sure there are no bugs in the code. Once the build job is complete, the deploy job takes over. It checks out a clean copy of the code because we do not want to push the node_modules folder from the previous job to the watched branch. The deploy job then configures access to GitHub so the pipeline script can push updates to the watched branch. The job then runs the Node.js script to push updates and trigger a new build of the Docker image.

The deploy job has a dependency on the build job so it will not run until the build job is complete. To see how the deployment changes after running both jobs, we need to add one more task to todo.js:

module.exports = [
  ...
  {
    id: 4,
    task: "Make Dinner"
  }
];

Adding a task means we need to update the test suite in __tests__/apiTest.js to check for four todo objects. Change line 15 to:

expect(res.body.length).toBe(4);

Save changes to the project and commit to your main branch in the remote repository.

Deployment Successful - CircleCI

Click the deploy job to see details:

Deployment Details - CircleCI

If you head over to the buildnodeapp on your registry page and click the Runs tab, you will find a new build of the Docker image running (cj2).

Docker Build - Azure

Now head over to your browser where the /todos endpoint was previously loaded. When you reload the page, you will find a new todo object.

Todos Updated - Azure

Conclusion

Containers give application developers and DevOps architects the ability to bridge gaps between development and production environments that may lead to bugs or apps not running as expected. In this tutorial, we have demonstrated how to use containers to make seamless deployments to Azure web apps. I hope you can use what you have learned to improve your own applications.

Happy coding!


Fikayo Adepoju is a Full-stack developer, technical writer and tech content creator proficient in Web and Mobile technologies and DevOps with over 10 years experience developing scalable distributed applications. With over 40 articles written for CircleCI, Twilio, Auth0 and The New Stack blogs and also on his personal Medium page, he loves to share his knowledge to as many developers as would benefit from it. You can also check out his video courses on Udemy.