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 containerize a Node.js application and use CI/CD to automatically deploy to Azure Web Apps via Azure Container Registry.

Prerequisites

To complete this tutorial, you will need:

  1. Node.js installed on your system (version >= 18.0.0)
  2. Azure account
  3. Free CircleCI account
  4. GitHub account
  5. Azure CLI installed
  6. Docker Desktop installed on your local machine. You can follow Docker’s tutorial for Windows or macOS.

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 a resource. Then select Containers > 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 nodedockerregistry --resource-group demoRG --sku standard --admin-enabled true

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

Getting an access key from the registry

Here, you will enable Docker access in the Azure Container Registry. This is crucial to the deployment process because it lets you remotely log into the Azure container registry through the CLI and push images to it.

To enable Docker access, open the registry, go to the Settings section and click Access Keys.

This will show you the registry name and login server. Enable the Admin user using the toggle button. Then, copy the username and any of the passwords, preferably the first one. Keep this handy because you will need it later in the tutorial.

Enable Admin and copy access key

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 https://github.com/CIRCLECI-GWP/new-node-azure-docker.git

Next, use cd new-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, open the Dockerfile (no extension) and ensure that its content is the same as shown below:

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 nodedockerregistry with the name you chose earlier. Run:

docker build -t nodedockerregistry.azurecr.io/mynodeimage:latest .

Based on the content of the application and the Dockerfile, this command will build the container image. Because it uses the registry name and login server you created earlier, it will easily map the container image with the Azure Container Registry.

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 once its been pushed.

Next, log into the Azure Container Registry and push the container image to it by issuing the following command from the terminal:

docker login -u DOCKER_USER -p DOCKER_PASS nodedockerregistry.azurecr.io

Replace nodedockerregistry.azurecr.io with your registry URL. Also, replace the following placeholders with appropriate values:

  • DOCKER_USER is the username for the container registry.
  • DOCKER_PASS is the password to the container registry.

After you log in, push the image to the Azure registry by running:

docker push nodedockerregistry.azurecr.io/mynodeimage:latest

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.

Creating an Azure Web App

Next, create an Azure Web App. From the Azure portal, click Home > Create a resource, then select Containers > Web App for Containers to create a new web app service instance.

You will be redirected to the Create Web App page. Select an Azure subscription, a resource group and enter the web app name. Create a new resource group if you don’t have one. Make sure the default, Docker container and Linux operating system are selected.

Creating web app

From the Docker tab, select the image source and its Docker image.

Select container image

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 usingnodedockerregistry 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 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, click the web app name, then go to the Deployment section. Click Deployment Center then scroll down the Settings tab. Turn on continuous deployment by selecting the radio button for it. Click Save.

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 .

Automating deployment to Azure using CircleCI

Your next step is to add the pipeline configuration for CircleCI. This configuration will automate testing and run the commands to build and push the container image to the Azure Container Registry.

At the root of your project, create a new folder named .circleci and add a new file config.yml within it. Open the new file and add the following content:

version: 2.1
orbs:
  docker: circleci/docker@2.2.0
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:18.0.0
    steps:
      - checkout
      - 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
  build-and-deploy-docker-image:
    executor:
      name: docker/docker
      tag: "3.6"
    steps:
      - checkout
      - docker/install-docker-tools
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build and push Docker image
          command: |
            docker build -t nodedockerregistry.azurecr.io/mynodeimage:latest .
            echo $DOCKER_PASS | docker login nodedockerregistry.azurecr.io -u $DOCKER_USER --password-stdin
            docker push nodedockerregistry.azurecr.io/mynodeimage:latest
workflows:
  version: 2.1
  build:
    jobs:
      - build
      - build-and-deploy-docker-image:
          requires:
            - build # only deploy once build job has completed

These scripts pull in the Docker orb from CircleCI. Then use its executor to install the tools required for Docker to build and push the image to the Azure Container Registry.

It also defines a workflow with two jobs: build and build-and-deploy-docker-image.

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 build-and-deploy-docker-image job takes over. It checks out a clean copy of the code, builds the docker image and push to Azure container registry.

The build-and-deploy-docker-image job has a dependency on the build job so it will not run until the build job is complete.

Set up a repository on GitHub and link the project to CircleCI. Review pushing your project to GitHub for instructions.

Connecting with CircleCI

Log into your CircleCI account. If you signed up with your GitHub account, all your repositories will be available on your project’s dashboard. Search for the deploy-dotnet-azure-instance project.

Select project

Click the Set Up Project button. You will be prompted about whether you have already defined the configuration file for CircleCI within your project. Enter the branch name (for the tutorial, we are using main). Click the Set Up Project button to complete the process.

This build job will pass indicating that the test was successful:

Build job successful

While the build-and-deploy-docker-image job will fail because it requires the Azure Container Registry credentials, and you haven’t added them yet.

Build failed

Creating environment variables

To fix the credential issue, click the Project Settings button, then click Environment Variables. Add these two new variables:

  • DOCKER_USER is the username for the container registry
  • DOCKER_PASS is the password for the container registry.

Click Rerun Workflow from Start.

Build successful

Your workflow will run successfully. You can make some changes to the codebase locally and push it to GitHub to further test the continuous deployment process.

That is it!

Go to the URL to review the live app: https://<APPLICATION_NAME>.azurewebsites.net/todos.

View App live on Azure

Note: Please note that your URL on Microsoft Azure should be different from the one shown above.

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__/todos.test.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.

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. The complete source code can be found here on GitHub.

Happy coding!


Fikayo Adepoju is a LinkedIn Learning (Lynda.com) Author, 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.

Read more posts by Fikayo Adepoju