Continuously deploy custom images to an Azure container registry
Fullstack Developer and Tech Author
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:
- Node.js installed on your system (version >= 10.3)
- An Azure account
- A CircleCI account
- A GitHub account
- Azure CLI installed on your system
- 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.
On the registry creation page, fill in the appropriate information including the name for your registry.
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 oftodo
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.
You can also visit the /todos
route to load the todo
objects.
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.
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.
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.
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.
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.
Click on the Set Up Project button to begin adding information.
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.
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.
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 accountGITHUB_USERNAME
is your GitHub usernameGITHUB_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!
You can click on the build
job to view the successful tests.
To see the details of the process, click the deploy
job (in your browser, go back one page).
Go to your registry task at Services -> Tasks and click the Runs tab. You will see the build running.
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!