The Azure container registry is Microsoft’s own hosting platform for Docker images. It is a private registry where you can store and manage private docker container images and other related artifacts. These images can then be pulled and run locally or used for container-based deployments to hosting platforms.

In this tutorial, you will learn how to create a custom docker image and continuously deploy it to an Azure container registry.

Prerequisites

To follow this tutorial, a few things are required:

  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 on your system
  6. Docker installed on your system

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

Creating a container registry on Azure

To begin, you need to create a container registry on Azure to store and build your Docker containers. Click the Create button on your Azure portal home page and go to Containers -> Container Registry.

Create registry - Azure

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

Create registry Page - Azure

Click the Review + Create button. On the review page, confirm your registry information and click Create to trigger the registry creation process.

If you prefer using the Azure CLI to create your resources, you can also use this command to create the registry:

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

Note that I have named my registry circlecigwpregistry in this process. As mentioned earlier, you might want to use a different name. Be sure to substitute your registry name in all the steps in this tutorial going forward.

Cloning the demo project

The next step is to get the Node.js project that will be used to build a Docker container and host it in the Azure container registry. Run the following command to clone the project:

git clone --single-branch --branch base-project https://github.com/CIRCLECI-GWP/azure-custom-images.git

This project is a basic Node.js API with two endpoints:

  • The root endpoint (/) simply prints out a welcome message
  • The /todos endpoint returns an array of todo task objects, and also contains a test suite for testing the /todos endpoint

Writing and building the custom Docker image

The next step is to write a custom Docker image to host the application in. Go into the root of the project folder (cd azure-custom-images) and create the Dockerfile file. Input the following code into the file:

FROM node:current-alpine

WORKDIR /app

COPY ./package.json ./

RUN npm install

COPY ./ ./

CMD ["npm", "start"]

The docker file uses node:current-alpine as its base image and sets the working directory in the container (that is to be created based on this image) to /app. The package.json file is first pulled into /app to install dependencies using npm install. Lastly, the remaining contents of the project are copied into the image and the application will be started using npm start at runtime. Refer to this page for more context about Docker images and containers.

Build the image by running the following command at the root of the project and tagging it with the name customnodeimage.

docker build -t customnodeimage .

Note the dot (.) at the end of the command. Do not forget to add this; it defines the build context to be the current directory.

Once built, you can then run the following command to run the Node.js application as a container based on the customnodeimage Docker image.

docker run -p 1337:1337 customnodeimage

The output of that command in your CLI should contain the line: Server running on localhost:1337 which indicates that the application is running.

The previous command forwards the application port in the container (1337) to port 1337 on your machine. You can navigate to http://localhost:1337 to view the application base endpoint.

App Local Run - Browser

You can also visit the /todos route to load the todo objects.

App Local Run - Browser

To get this image to your registry, run the following command to build the image on ACR from the Dockerfile. Replace <container_registry_name> with the name you gave the registry you created.

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

You can now view this image on your registry. Click your registry on the Azure resources page. It should be listed under Recent resources on the Azure portal home page. Go to Services -> Repositories.

Image - ACR

Setting up a build task for the container

When any update is made to the application code or the Dockerfile, we want the updates pushed and built on ACR, so that there is always an updated image on ACR. That is continuous deployment.

Fortunately, ACR can be configured to watch a branch on a remote repository and trigger builds based on updates to that branch. The strategy for continuous deployment of the image is to create a branch on the remote repository for the image that ACR will watch to trigger builds. Once the application tests pass, updates are then automatically pushed to the watched branch to trigger a rebuild of the image using the updated content.

To begin, run the command rm -rf .git at the root of the project to remove any existing git history, then push the project to GitHub.

Next, on the GitHub repository, create a buildimage branch from the main branch.

Create branch - GitHub

To watch the branch just created, you will need an ACR task. To create an ACR task, you will need a GitHub personal access token.

To create a GitHub token, go to Settings -> Developer Settings on your GitHub page. On the side menu, click Personal access tokens, then click the Generate new token button. Confirm your password to proceed and you will be taken to the token creation page.

Enter a description for the key. For this demo, give it all repo permissions (except the delete_repo permission). Also give it the webhook permission. The webhook permission is the most important one, because it has the ability to create a webhook in your repo that will be used to signal ACR to trigger a new build of the Docker image.

GitHub token - GitHub

Click the Generate token button. Copy the token once it is generated. It will not be displayed again.

To create an ACR task, run the following command at the root of your project using your repository address, your registry name, and GitHub access token:

az acr task create --registry <container_registry_name> --name buildcustomimage --image customnodeimage --context <your_github_repo>#buildimage --file Dockerfile --git-access-token <access_token>

For the registry and repository in this tutorial, that will be:

az acr task create --registry circlecigwpregistry --name buildcustomimage --image customnodeimage --context https://github.com/CIRCLECI-GWP/azure-custom-images.git#buildimage --file Dockerfile --git-access-token [My_TOKEN]

This will create a buildcustomimage task that will rebuild the customnodeimage image each time updates are pushed to the buildimage branch. To view your newly created task, go to the registry page and go to Services -> Tasks.

ACR task - Azure

Creating the CircleCI project

Now, go to the Projects page on the CircleCI dashboard. Select the GitHub account you are using for this tutorial to add the project.

Add Project - CircleCI

Click on the Set Up Project button to begin adding information.

Add Config - CircleCI

On the set up page, click Use Existing Config to indicate that you are adding a configuration file manually, not using the sample. You are prompted to either download a configuration file for the pipeline or start building.

Build Prompt - CircleCI

Click Start Building to initiate the build. This build will fail because you have not set up the configuration file yet. We will complete this task later in this tutorial.

Setting up GitHub authentication

Part of the continuous deployment strategy is to make a push to the buildimage branch from the pipeline configuration that will be written for this project, thus authenticated access is required for CircleCI to access the repository on GitHub.

Fortunately, CircleCI provides a way to add a User API Key to achieve that. On your project, go to Project Settings -> SSH Keys and go to the User Key section. Click the Authorize with GitHub button to make a connection to GitHub.

Once this is complete, an Add User Key button appears in the User API Key section.

Add User key - CircleCI

A fingerprint is generated which will be used later in the deployment pipeline. 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 safe to put these in environment variables. On the side menu of your project settings click Environment Variables and add these:

  • GITHUB_EMAIL is the email of your connected GitHub account
  • GITHUB_USERNAME is your GitHub username
  • GITHUB_FINGERPRINT is the authentication fingerprint that was generated

Writing the pipeline script

The final task in this tutorial is to write the continuous deployment pipeline script for the custom docker image.

To enable the pipeline script to push updates to the buildimage branch of the GitHub repo, we will use the node package gh-pages. This package is configurable for pushing files from one branch to another in any given repository.

Install the package as a development dependency using the following command at the root of the project:

npm install gh-pages --save-dev

Next, add a pushtobuild script in the package.json file:

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

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

Now you can begin writing the pipeline script. At the root of your project, create a folder named .circleci and inside it, 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: Build Image on Azure Container Registry Task
          command: npm run pushtobuild

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.

The build job checks out the code from the remote repository, installs required dependencies, and runs the tests to ensure that no bugs are contained in the code. Once the build job is complete, the deploy job takes over and checks out a clean copy of the code (we do not want to push the node_modules folder from the previous job to the watched branch). It then configures access to GitHub to enable the pipeline script to push updates to the watched branch (buildimage). Finally, the deploy job runs the Node.js script to push updates and trigger a new build of the docker image.

When you commit your updates and push to the GitHub repository, you will have a successful workflow!

Build Successful - CircleCI

You can click on the build job to view the successful tests.

Build Details - CircleCI

To see the details of the process, click the deploy job (in your browser, go back one page).

Deploy Details - CircleCI

Go to your registry task at Services -> Tasks and click the Runs tab. You will see the build running.

Image build running - Azure

After a while, the build will be complete and marked as Succeeded.

When you go to your image at Services -> Repositories and click the image, you will notice that the Last updated date has changed, indicating that the new build is now the most recent.

Conclusion

Azure Container Registry gives you the ability to store your container images, enabling fast and scalable retrieval of container workloads. In this tutorial, you have built a custom docker image and continuously deployed it to ACR each time your application code or Dockerfile changes. To get started applying what you have learned to your own work, sign up for your CircleCI free trial today.

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.