Deploy .NET Core Application To Linux Containers On Azure Web App - Part One

Setting the Context

Containers are heavily used and are an integral part of any deployment or DevOps process. Azure DevOps provides wonderful support for Containers. Azure Web Apps have evolved to be very powerful and are not just meant to host code. They can run containerized applications as well. Deployed container on an Azure Web App takes advantage of underlying auto-scaling and load balancing capability thus making the things seamless.

In the first part of this series, we will build a Continuous Integration Pipeline that will pull the source code from Azure Repos, build and package the application in a Docker Container and then push it to Azure Container Registry.

In the second part, we will build a Continuous Deployment Pipeline that will pull the image from the Azure Container Registry and deploy it to Azure Web App for Containers.
In the third and final part of this series, we will explore other easy options that Azure Web App provides out of the box to enable Continuous Integration and Continuous Deployment.
Figure 1 illustrates the takeaway from the series in a nutshell.

This series does not get into basic concepts for Azure DevOps, Docker or Web App for Containers. As a prerequisite, it would be a good idea to get an understanding of these topics before diving deep here.

Create an ASP.NET Core Web Application that can be hosted on Linux Docker Container

Let us start with creating an ASP.NET Core Web Application that can be hosted on Linux Docker Container. Visual Studio 2019 Community Edition is used here as IDE.

Open Visual Studio and Click on Create new project

Select ASP.NET Core Web Application and click Next.

Provide Project Name and click on Create.

Select Web Application (Model -View-Container), check the checkbox Enable Docker Support and select Linux in the dropdown. Click on Create.
We can see Dockerfile gets generated as part of the solution. 
Comments are added in the Dockerfile that details out what each step does.
  1. # Use microsoft/dotnet:2.1-aspnetcore-runtime-stretch-slim 
  2. # from public Docker registry as base image.   
  3. # Alias the image as base  
  4. FROM microsoft/dotnet:2.1-aspnetcore-runtime-stretch-slim AS base  
  5. # Set /app as working directory.  
  6. WORKDIR /app  
  7. # Expose port 80 on Image.  
  8. EXPOSE 80  
  9. # Expose port 443 on Image.  
  10. EXPOSE 443  
  11. # Use microsoft/dotnet:2.1-sdk-stretch from public Docker registry 
  12. # as build image. 
  13. # Application will be built on this image.   
  14. # Alias the image as build.   
  15. FROM microsoft/dotnet:2.1-sdk-stretch AS build  
  16. # Set working directory as /src  
  17. WORKDIR /src  
  18. # Copy from LinuxAppForContainer/LinuxAppForContainer.csproj 
  19. on local disk where Docker file is present   
  20. # to Image folder LinuxAppForContainer in /src folder  
  21. COPY ["LinuxAppForContainer/LinuxAppForContainer.csproj""LinuxAppForContainer/"]  
  22. # Restore tools and packages for the solution on the local disk  
  23. RUN dotnet restore "LinuxAppForContainer/LinuxAppForContainer.csproj"  
  24. # Copy all files/folders recursively from current folder where the docker 
  25. # file is present on local directory to /src   
  26. COPY . .  
  27. # et working directory as /src/LinuxAppForContainer  
  28. WORKDIR "/src/LinuxAppForContainer"  
  29. # Build solution in Release mode and place it in /app folder  
  30. RUN dotnet build "LinuxAppForContainer.csproj" -c Release -o /app  
  31. # Publish build to /app folder and alias build as publish  
  32. FROM build AS publish  
  33. RUN dotnet publish "LinuxAppForContainer.csproj" -c Release -o /app  
  35. # Prepare the final base image. Copy /app from image named as publish to base 
  36. # image /app directory and alias it final  
  37. FROM base AS final  
  38. WORKDIR /app  
  39. COPY --from=publish /app .  
  40. ENTRYPOINT ["dotnet""LinuxAppForContainer.dll"]  

Move the Dockerfile to the folder where the solution file is generated. This is a very important step for the Dockerfile to work as expected and this is being generated inside the wrong folder. The Dockerfile should be present in the folder where the solution (.sln) file is present.

Add the Dockerfile to the solution at solution level (Right click on Solution and then Add). This will get added as a solution item.

Now let us add this solution and all its files to source control and commit it to Azure Repo.

Create Azure Container Registry

Azure Container Registry is a private repository for Docker Container images on Azure. Now let us create an Azure Container Registry.

Go to Azure portal and search for Container Registry in Marketplace. Click on Create.

Provide unique Registry Name, Resource group name. Enable Admin user. Click Create.


Create a Continuous Integration Pipeline in Azure DevOps

Go to the project inside Azure DevOps.

Click on Pipelines and then click on Builds

Select Source as Azure Repos Git as we have committed our source code to Azure Repos. Select Team project as LinuxAppForContainer as this is the project name where the source code has been pushed in Azure DevOps environment. Select Repository as LinuxAppForContainer as the code is pushed inside this repository. Click on Continue.

The select template as Docker Container. Click Apply.


Keep Agent pool as Hosted Ubuntu 1604 as we are going to build a Linux Container. Click on Build an image.

Provide settings as in the below screen. Select Azure subscription and click on Authorize. It will ask you to log in to Azure subscription. Once login is successful and the subscription is authorized to select Azure Container Registry, select Action as Build an image.
Click on Push an image. Select Azure subscription, Azure Container Registry, and Action as Push an image.
Click on Save & queue. The build gets triggered.

You can click on Build number to check for the progress of the build.

Once the build is successful, go to the Azure portal and open the Azure Container Registry. We can see the Docker image present inside Repositories. This image is built and pushed by the Azure DevOps build.
To enable Continuous Integration to go back to the Build Triggers tab we created on Azure DevOps and select checkbox Enable continuous integration and save. This will make sure that every time we check in, the build will get triggered and a newer image will be pushed to Azure Container Registry.

Create a Web App and consume the image pushed to AzureContainer Registry

Go to Azure Marketplace and click on Web App.

Provide App name, Subscription, Resource Group, OS (Linux), Publish (Docker Image).

Click on Configure Container. Select Single Container tab -> Azure Container Registry. Select the Registry as our Azure Container Registry we created. Select Image and Tag. Click Apply.

Click on Create to create the Web App. Once the Web App is created launch the Web App url. You can see that the Website we packaged as Docker Image is up and running.


Winding up

In this article, we created a Continuous Integration build that pulled the source code from Azure repos, packaged the build as Docker Image and pushed to Azure Container Registry. In the next article, we will see how to create a Continuous Deployment build that will pull images from the Azure Container Registry and deploy it to the Azure Web App.