Skip to content

Build and Deploy an ASP .Net Core Web Application as Docker Container using Microsoft Azure – Part 2

Last updated on April 24, 2019

 

Introduction

This is the second article in the series, and here we are going to setup a Microsoft Azure DevOps build pipeline to automate the tasks we did manually in first article of the series. Each time we push a change to the master branch, the build will be triggered to build our application, then build a Docker image and push it to Docker Hub.

If you have been following along, you must have:

  • a GitHub repository
  • a working web application in ASP .Net Core (or something similar)
  • a Docker Image for the application
  • a running container to host your application locally

Three parts in series:

 

Docker Registry

First, you need to setup an account at Docker Hub. After that create a repository, where you can keep Docker images for your application.

You already have a Docker image, built in previous steps, that works for well. So now, you can tag that image with the repository name. However, the Docker client running on your local machine needs to connect with the Docker Hub account, before you can push the image.

The following commands tag the local image for Docker Hub repository, authorize the Docker client and finally push the image to Docker Hub.

$ docker tag <image-name> <namespace>/<repository>:<tag> 

// example
$ docker tag webapp quickdevnotes/webapp:v1

// login to your Docker Hub account
$ docker login 
username:
password:
// push the to Docker Hub
$ docker push quickdevnotes/webapp:v1

Note that I’m using a public repository at Docker Hub. If you are using a private repository, then you will have to first login using the docker login command and then push the image. Otherwise, the push will fail with authorization error.

 

Azure DevOps – Project

Now comes the interesting part. To start using the Azure DevOps pipelines, you first need to have a user account. If you already have an account, great, you are good to start or you can register here.

Once you are in, it asks you to create an organization and where you want to host your project. Name it anything you like and select a region suitable to your need:

Create organization at Azure DevOps
Create organization at Azure DevOps

Next, we have to create a project under the organization. Select +Create Project from the top right, and provide the required details.

Create a project in Azure DevOps
Create a project in Azure DevOps

From the “Advanced” section we don’t need anything for the purpose of this series. So, I leave the choice up to you.

 

Continuous Integration (Build Pipeline)

It’s finally time to automate the manual steps. From Pipelines > Builds select New Pipeline.

Where is your code?

The first thing Azure pipeline needs is to connect with your application code repository. So, on the Connect tab, select GitHub a connection to your GitHub.

Select code source for Build Pipeline
Select code source for Build Pipeline

After that you will be prompted with OAuth authentication to authorize Azure Pipelines for accessing the GitHub repository. Grant the authorization and provide credentials as required.

Authorize Azure Pipelines
Authorize Azure Pipelines

Select a repository

Once the authorization completes, you can see a list of repositories from the GitHub account. From the list of repositories select your application repository, which then prompts to Install Azure Pipelines. Use the “only select repositories” option and from the drop-down select your application repository.

Select application repository
Select application repository

If you want to install Azure Pipelines for all current and future repositories select “All repositories” and click install.

Install Azure Pipelines
Install Azure Pipelines

azure-pipelines.yml

The Azure pipeline is smart enough to analyse your application repository and provide a basic azure-pipelines.yml file. This yml file lies at the root of your GitHub repository and is used by the Azure build pipeline to perform certain tasks like building the application, executing tests, building Docker Image and many more.

Here is the azure-pipelines.yml file for my build pipeline. I know, it can be overwhelming; but everything will be clear after we talk about each task in detail.

trigger:
- master

pool:
  vmImage: 'Ubuntu-16.04'

variables:
  imageName: 'quickdevnotes/webapp:$(build.buildNumber)'

steps:
- script: dotnet test WebApp.Tests/WebApp.Tests.csproj --logger trx
  displayName: Run unit tests
- task: PublishTestResults@2
  condition: succeededOrFailed()
  inputs:
    testRunner: VSTest
    testResultsFiles: '**/*.trx'
- task: Docker@1
  displayName: Build an image
  inputs:
    command: Build an image
    containerregistrytype: Container Registry
    dockerRegistryEndpoint: DockerHub
    dockerFile: Dockerfile
    imageName: $(imageName)
    imageNamesPath: 
    restartPolicy: always
- task: Docker@1
  displayName: Push an image
  inputs:
    command: Push an image
    containerregistrytype: Container Registry
    dockerRegistryEndpoint: DockerHub
    dockerFile: Dockerfile
    imageName: $(imageName)
    imageNamesPath: 
    restartPolicy: always

Note that the file generated for your build pipeline will be different from what you see here. This is, because I have updated the file with tasks required to achieve our end goal.

 

CI Tasks – a close look

Let’s take a closer look at the tasks defined in the above build file. We will stay at a high level though, just to keep things simple for this series.

trigger:
- master

The trigger specifies, pushes to which branch will trigger the continuous integration pipeline to run. In my case it’s master branch.

If you want to use a different branch just change the name of the branch. If we do not specify any branch, pushes to any branch will trigger a build.

pool:
vmImage: 'Ubuntu-16.04'

The above lines tell which agent pool to use for a job/task of the pipeline.

variables:
imageName: 'quickdevnotes/webapp:$(build.buildNumber)'

We use the variables section to declare any variables we want to use in our pipeline. For instance, here I’m creating a variable imageName which will be set to name of the Docker image that I want to create.

The Continuous Integration best practices recommend to using $(build.buildNumber) to tag your Docker images. Because it makes it easy to update or rollback any changes.

- script: dotnet test WebApp.Tests/WebApp.Tests.csproj --logger trx
  displayName: Run unit tests
- task: PublishTestResults@2
  condition: succeededOrFailed()
  inputs:
    testRunner: VSTest
    testResultsFiles: '**/*.trx'

I have also added some basic unit tests to my application. With the above script, these tests will execute each time the build is run. We can also get test reports out of these test runs.

- task: Docker@1
  displayName: Build an image
  inputs:
    command: Build an image
    containerregistrytype: Container Registry
    dockerRegistryEndpoint: DockerHub
    dockerFile: Dockerfile
    imageName: $(imageName)
    imageNamesPath: 
    restartPolicy: always
- task: Docker@1
  displayName: Push an image
  inputs:
    command: Push an image
    containerregistrytype: Container Registry
    dockerRegistryEndpoint: DockerHub
    dockerFile: Dockerfile
    imageName: $(imageName)
    imageNamesPath: 
    restartPolicy: always

The first in the above two tasks, builds a Docker image using the Dockerfile available at the root of the repository. After building the image, the second task pushes that image to our repository on Docker Hub.

To read more about the different tasks and available options, please refer the docs.

In order to push an image to your Docker Hub repository, the Docker client running on the build agent needs to authorize first. Similar to what you did, while pushing the image manually.

- task: Docker@1
...
    dockerRegistryEndpoint: DockerHub
...

The dockerRegistryEndpoint is set to a docker registry service connection that holds the credentials for Docker Hub account and is used for authorization. Let’s setup that next.

 

Docker Registry Service Connection

From bottom left of navigation pane, select Project Settings. Then under Pipelines, select Service connections and you should see your GitHub connection here.

To a Docker Registry connection, click New service connection and from the list select Docker Registry.

Docker Registry Service Connection
Docker Registry Service Connection

On the next dialog window, select Docker Hub  as the Registry Type and set DockerHub (must be same as  dockerRegistryEndpoint in build task) as the Connection Name.

Then, provide the Docker ID and Password of your Docker Hub account. Email is optional. Please ensure “Allow all pipelines to use this connection” is checked.

Docker Registry Service Connection Details
Docker Registry Service Connection Details

You can verify the connection by using the “Verify this connection” link. Click “OK” and we are good to go.

 

Testing the Build Pipeline

I believe, by now you have enough information to customize your continuous integration pipeline. Alright then, it’s time to test if it does what we expect.

Go to Pipelines > Builds and select Queue to queue a build. If you don’t get any errors, you must see a build in progress. Here is the output from my build pipeline, after a successful build:

Successful build, triggered manually
Successful build, triggered manually

The final task in the pipeline has successfully pushed the newly built Docker image to Docker Hub. Notice the image tag, which is equal to the build number in the above image:

Image pushed to Docker Hub
Image pushed to Docker Hub

To make a final test, change something in application code and push it to the master branch. This will trigger the build and you will see the commit message as build title:

Build Pipeline triggered by push to master branch
Build Pipeline triggered by push to master branch

Notice how the build description tells you about the build trigger, and this proves it is not manual trigger.

Congratulations!!

 

Conclusion

In this article, we have successfully setup a working continuous integration build pipeline with Microsoft Azure DevOps. Starting from building a .Net Core web application, to setting up a CI pipeline, we are half way through.

Next, we will setup a release pipeline to deploy our application as Docker container on Azure Web App Service. It’s going to be fun.

 

Published inDevOpsDockerMicrosoft Azure