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:
- Node.js installed on your system (version >= 10.3)
- An Azure account
- A CircleCI account
- A GitHub account
- 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.
On the registry creation page, fill in the information, including the name for your registry.
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
.
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.
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.
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.
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 using
fikayoregistry
in this tutorial) - Use
mynodeimage
for the Image field - For Tag, select
latest
- Leave
Startup Command
empty; your Dockerfile already has anENTRYPOINT
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.
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.
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.
This branch will be the one that is watched to trigger rebuilds on the Docker
image.
From the CircleCI dashboard, click Add Projects.
Click Set Up Project.
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.
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 accountGITHUB_USERNAME
: Your GitHub usernameGITHUB_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.
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.
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.
Click the deploy
job to see details:
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
).
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.
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 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.