Use Azure Pipelines to deploy .NET application to Azure App Service

Andrey Kukharenko
16 min readApr 29, 2023

--

The story about using Azure Pipelines (YAML) to build and deploy .NET web application.

Notes and assumptions

Notes:

  1. Here will not be provided complete instructions for all the steps because main idea is to cover pipeline stuff. Some descriptions and steps will be described but you need to be familiar with the main and basic concepts (.net development, dotnet cli, etc.).
  2. All the related topics like creating the App Service and configuring Azure DevOps can be found in many other articles and documentation.

Assumptions:

  1. Be familiar with the .NET development.
  2. Be familiar with the dotnet cli.
  3. Be familiar with Git.
  4. Have configured Azure DevOps with repository and pipelines activated for the project.
  5. Have active account for Azure with subscription.

Parts:

  1. Use Azure Pipelines to deploy .NET application to Azure App Service
  2. Use Azure Pipelines to deploy Angular application to Azure App Service
  3. Use Azure Pipelines to deploy Function App

Introduction

In development process contains many steps where one of them are delivery part — application need to be places somewhere to be used by end users and running and provide actual functionality. This is part of the DevOps culture and general SDLC cycle.

This part usually named as Build and Deploy or in more modern way CI (continuous integration) and CD (continuous delivery). The main idea is to automate the process as much as possible and all the time when we push the new code build and release process can run automatically to deliver changes to source location.

This article is about using Azure Pipelines in a good way (in April 2023, because some changes can happened later) for automate the build/deploy process with the code (and some manual actions).

Here will be discussed the next topics:

  1. Azure App Service — host application as service on the Azure.
  2. Azure DevOps — some configuration stuff.
  3. Azure Pipelines — build and deployment configuration.

1 Microsoft Azure

The second popular cloud provided based on statistics and usages. The cloud provided a lot of services and things. One of the most used and popular is App Service (used to host web apps, functions, jobs). In this sample and article we don’t want to discuss it in details because main goal is to show pipelines. There are a lot of books, videos and articles, so you can use all of them if not familiar.

1.1 Create the service in Azure portal

This video provide comprehensive information on this topic.

To create the new resource need to go to the portal.azure.com, create the new Resource Group, create App Service Plan, App Service, all in this group. Copy the name or remember because it need to be used later in variables go specify where to deploy the application.

For this sample will be used Windows OS for App Service. Other possible types is Linux or Containers.

Sample resource group with resources

Suggestion here is to use strong naming convention for group and all resources. For some services like storage account it need to be without spaces and other special characters. So would be good to read documentation and articles on this topic.

2 Azure DevOps

This is the main service can be used for some of the projects to store the code and trash the work. Assumption is having the repository with the code (at least) for sample application.

In the system we need to turn on the Pipelines in the project settings. This settings can be selected via creating the project or later to use only needed functionality. Select “Project settings” → “Overview”.

For our purposes and work we need:

  1. Azure Repos — for Git repositories with the code.
  2. Azure Pipelines — for CI/CD stuff.

Other services not required in general to play within the topic of the article.

2.1 Service connection

To use deployment functionality it need to configure the service connection to use to deploy to Azure in all the pipelines. Go to the “Project Settings” → “Service Connections”.

This is required thing that need to be done before creating the pipelines for deployment!

Sample of the service connections added in the project

To add the new connection it need to have the Azure account, usually it Microsoft account for all services (DevOps, Azure, etc.).

To add the new need to select “New service connection” → “Azure Resource Manager”. Other ones also can be used but mostly for old and more custom things with manual configuration. Depending on the permissions you have and it may be vary configuration options.

Add the new Service Connection to Azure

Select the “Service principal (automatic)” for most basic usages (if you are admin will all permission in Azure).

Select the authentication method for Azure

Provide the connection details. Here need to select active subscription from Azure (it will load automatically based on your account), add the name (unique), description. Resource group is not required to select — only in case if connection need to use only inside specific one group. Also check the box to grant access to al pipelines — it will help to use this connection in all pipelines that you are will built.

Provide the connection settings and name (unique)

After this steps the new connection can be used in pipelines to deploy to Azure. Or it also can be used in Classic Releases to deploy as well because internally it use the same logic and tasks.

3 Azure Pipeline

The documentation provide the simple description for the service:

Azure Pipelines supports continuous integration (CI) and continuous delivery (CD) to continuously test, build, and deploy your code. You accomplish this by defining a pipeline.

The latest way to build pipelines is with the YAML pipeline editor. You can also use Classic pipelines with the Classic editor.

So it means we can use it to deliver application for end users and automate some process like build, test, deploy, etc. It can be configured in very different ways to able to have good and powerful DevOps pipeline and development process in general (simple, fast, as code).

Here will be covered only the YAML pipeline that allow to use the code and Git to manage the files and history and have in one place with possible sharing the functionality.

The basic flow of working with pipelines:

You define your pipeline in a YAML file called azure-pipelines.yml with the rest of your app.

There are 2 main steps:

  1. Build — build the application, can run linting, tests, etc. Result here — artifacts with application to run or do deployment.
  2. Deploy — place the application artifacts to hosted location.

There are different concepts inside but very short:

  1. pipeline can contain stages
  2. stages can be executed one by one
  3. stage can depends on previous stage(-s)
  4. each stage can contain jobs
  5. jobs can be executed in parallel
  6. each job can contain steps
  7. step is the basic operation to do (like install packages, build, publish, etc.)
  8. step is the task from the list of available tasks with parameters

On the image bellow provided simple presentation for the pipeline structure and elements: stages, jobs, steps (tasks).

Visual presentation of the pipeline structure and elements

So if we take a look in pipelines it can be used stages — multi-staged pipeline where each step is running one by one.

To have better and allow to customize the steps is to use the templates functionality — each step can be stored as separate file as template. More details provided in the documentation.

To prepare pipeline need to do some steps:

  1. Create the repository — in this point we already have the one.
  2. Create the environments — prepare the configuration for environment definitions to deploy the application, configure them (like approvals, etc.)
  3. Create the library with variable groups — to use variables in the pipeline and can be edit without code changes.
  4. Create the pipeline — prepare files in the repo and select them for new pipeline.
  5. Configure security — access to the library, pipeline permissions for environment to able to consume them.

Here we will look at the modern way for deployment with YAML and Environments. This is not the same as classis build and release with another approach and configuration.

3.1 Create the Environment(-s)

To deploy from Pipeline we need to configure list of environments to be used to do deployment jobs — special job type to handle environment configurations.

To create the new environment need to go to the “Project” → “Pipelines” → “Environments” and click “Create Environment” button.

Creating the new environment

After this we can see list of them and with latest statuses (if already deployed something).

You can also configure that in order to be deployed to some of these environments, someone needs to approve. For example, let’s create this configuration to say that in order to deploy to the “QA” environment, some user must approve it. In order to do that, click in the QA environment and go to the configuration → “Approvals and checks”.

Select Approvals and checks

If nothing configured yet you will see the empty screen.

Empty list of configs for environment

To add the approvers need to select the users from the directory and allow them to approve the deployment to this environment.

Configure approvers

In case you select more than one user, you can also configure if ALL users must approve, or if one, two, or how many users you want must approve.

3.2 Create the library

In our sample we will use library to store some variables for pipeline to provide different values for different environments. It will help to have one code base but with different inputs.

To create the group need to go to the “Pipelines” → “Library”.

Create the new group

Configure the group: name, variables, descriptions, etc.

Create the variables group and define variables

Create the variable groups for all environments.

Sample variable groups created

This is main idea to have separate parameters/values for all environments.

3.3 Create the pipeline

First we need to create the files with the logic and code. After this configure pipeline and select appropriate file from the repository — azure-pilelines.yml.

3.3.1 Code for pipeline

Sample pipeline for .NET application contains few files in the repo. All the files placed in the build folder in the repository root with solution and some other folders and items.

Pipeline with stages: Build and Deploy used here.

Files in this sample (as template):

  1. azure-pipelines.yml — main file to provide the structure and workflow.
  2. pipelines/build.yml — stage to define the build steps and what the jobs need to run.
  3. pipelines/release.yml — stage for deployment stuff, define jobs and rules.
  4. pipelines/build-dotnet.yml — template file with common steps to build the .NET application using dotnet cli.
  5. pipelines/deploy-webapp.yml — template file with common steps to deploy application to Azure App Service from the package (.zip archive).

All the steps to build the .NET application based on usage of dotnet cli. You can get more in documentation. But basically it’s the same commands used in local development.

File azure-pipelines.yml:

# Parameters for pipeline
parameters:
- name: environment
displayName: Environment
type: string
default: BuildOnly
values:
- BuildOnly
- Development
- name: projectName
displayName: ProjectName
type: string
default: <Your_project_name> # <-replace value here!
- name: skipLint
displayName: SkipLint
type: boolean
default: false

# no automatically triggers
trigger:
- none

variables:
- group: Deploy-Environment-Shared

# stages - list of the jobs to do step by step
stages:
# Build and Lint (and other stuff) stage
- template: pipelines/build.yml
parameters:
Environment: ${{ parameters.Environment }}
ProjectName: ${{ parameters.ProjectName }}
SkipLint: ${{ parameters.SkipLint }}

- ${{ if not(eq(parameters.Environment,'BuildOnly')) }}:
- template: pipelines/release.yml
parameters:
Environment: ${{ parameters.Environment }}
ProjectName: ${{ parameters.ProjectName }}

File pipelines/build.yml:

stages:
- stage: Build
displayName: Build${{ parameters.ProjectName }}

# Use multiple jobs, so the linter can work in parallel to the build.
# This also allows to run the Linter on Linux whereas you build can run on Windows or Mac.
jobs:
# Lint the code with Super-Linter from GitHub
- job: lint
displayName: Lint code base
condition: eq(${{ parameters.SkipLint }}, false)
pool:
vmImage: 'ubuntu-latest'
steps:
- script: docker pull github/super-linter:latest
displayName: Pull GitHub Super-Linter image
- script: >-
docker run \
-e RUN_LOCAL=true \
-e VALIDATE_JSCPD=false \
-e VALIDATE_MARKDOWN=false \
-e VALIDATE_EDITORCONFIG=false \
-v $(System.DefaultWorkingDirectory):/tmp/lint \
github/super-linter
displayName: 'Run GitHub Super-Linter'
continueOnError: true

# Do main build action
- job: build
displayName: Build and Test, create artifacts
pool:
vmImage: 'windows-latest'
variables:
BuildConfiguration: 'Release'
steps:
- template: build-dotnet.yml
parameters:
ProjectServiceName: ${{ parameters.ProjectName }}
RestoreBuildProjects: '**/*.csproj'
TestProjects: 'tests/**/*.csproj'
DotnetVersion: '7.x'
ServicePublishProjects: '**/<your project name>.csproj' # replace the value here

File pipelines/build-dotnet.yml:

steps:
- checkout: self
clean: true

# Install anc cache .NET SDK
- task: UseDotNet@2
displayName: 'Install Dotnet Core cli'
inputs:
version: ${{ parameters.DotnetVersion }}

# Restore NuGet packages
- task: DotNetCoreCLI@2
displayName: 'Dotnet restore'
inputs:
command: restore
projects: |
${{ parameters.RestoreBuildProjects }}
${{ parameters.TestProjects }}

# Build the project
- task: DotNetCoreCLI@2
displayName: 'Dotnet build'
inputs:
projects: ${{ parameters.ServicePublishProjects }}
arguments: --configuration $(BuildConfiguration) --no-restore

# Run the tests
- task: DotNetCoreCLI@2
condition: ne('${{ parameters.TestProjects }}', '')
displayName: 'Dotnet Test'
inputs:
command: test
projects: ${{ parameters.TestProjects }}
arguments: '--configuration $(BuildConfiguration) --no-restore --collect "Code coverage"'

# Publish web application (to use later for deployment)
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
publishWebProjects: false
zipAfterPublish: true
projects: ${{ parameters.ServicePublishProjects }}
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory) --no-restore'

# Publish artifacts after job finished within pipeline
- task: PublishPipelineArtifact@1
displayName: Publish pipeline artifacts
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifactName: '${{ parameters.ProjectServiceName }}'
publishLocation: 'pipeline'

File pipelines/release.yml:

stages:
# Main deployment stage per one selected environment
- stage: Deploy
displayName: Deploy${{ parameters.ProjectName }}
variables:
- group: Deploy-Environment-${{ parameters.Environment }}
jobs:
# Do actual web app deployment to Azure
- deployment: DeployWebApp
displayName: Deploy WebApp to Azure
pool:
vmImage: 'windows-latest'
environment: ${{ parameters.Environment }}
strategy:
runOnce:
deploy:
steps:
- template: deploy-webapp.yml
parameters:
Environment: ${{ parameters.Environment }}
ProjectName: ${{ parameters.ProjectName }}
PackageName: '<your project name for package (zip)>' # replace the value here

File pipelines/deploy-webapp.yml:

steps:
- checkout: self
clean: true

# Download pipelines artifacts
- task: DownloadPipelineArtifact@2
inputs:
artifactName: '${{ parameters.ProjectName }}'
buildType: 'current'
targetPath: '$(Build.ArtifactStagingDirectory)\${{ parameters.ProjectName }}'

# Deploy to Azure App Service
- task: AzureWebApp@1
displayName: 'Deploy to $(AppServiceNameBackend)'
inputs:
azureSubscription: '$(AzureServiceConnection)'
appType: 'webApp'
appName: '$(AppServiceNameBackend)'
package: '$(Build.ArtifactStagingDirectory)\${{ parameters.ProjectName }}\${{ parameters.PackageName }}.zip'

This files for pipeline can be used for most of the project, just need to update some variables. And of cause if needs to add some special customization it can be added and modified as needed.

3.3.2 Create pipeline

To use the code in the repository it need to do one more step — create the actual pipeline to run and see the results.

Create the new pipeline

Select the repository to use. It’s possible to use the external services but in our sample and process we use internal repos for simplicity. The main idea is the same because most of other services provided the own CI/CD tools (GitHub Actions, etc.).

Select sources location

Select repository from the list. Usually you will have one repository with the same name as project (by default) but it can be more that one because project as aggregator for all the related work — it can be backend, frontend, infrastructure, etc.

Sample repo with the application

Select the existing pipeline (in case if all the code already pushed to the repo) or it possible to select from template. In such case the pipeline file will include only one root file (azure-pipelines.yml) with sample code.

Select from template or existed file

Select the file from repository in one of the branch. File in sample placed in the “build” folder with all related files.

Select the new pipeline from specific branch

Finally need to review result and able to “Run” or “Save”. After saving it allows to run as usual. Because no automatic trigger configured the pipeline need to run manually.

Review and save new pipeline

This step prepare pipeline to use and will show the new one in the list.

New pipeline

My suggestion is also use folders to organize the pipelines definitions. Go to “All” and click “New Folder” button.

Create the new folder based on the topic of group if you have many items

It will help to search needed pipeline, group them as you wish, move old to folder and new to the new one, etc.

To move to the folder need to select pipeline and select “Rename/ Move”.

Select pipeline to rename or move to folder
Select folder to move

3.4 Additional configuration

To run the pipeline and have deploy successfully it need to configure some security things:

  1. Pipeline permissions for library — for each groups in use.
  2. Security for environment — for each of them.

Complete pipeline will be ready after this steps and ready to run.

3.4.1 Configuring the library access

Each variable group need to able to use in pipelines. So it need to go to he each pipeline to setup permissions and select all of them.

Configure permissions for pipelines to use variables in the group

Add pipelines from the list.

Select all YAML pipelines to have access to the variables

After this selected pipelines will have access to the variables.

3.4.2 Configuring the environment

Select the environment, settings, security. It will open page with user and pipeline permissions.

Security for environment

Add the list of pipelines (if one or more) to the list.

Select all pipelines from the list on “+”

After all this steps the new pipeline can run and do the work.

3.5 Run pipeline

To run the pipeline it need to go to selected pipeline and click on “Run pipeline” button.

Because of pipeline use parameters it will show them to user in the UI to able to select and change if needed.

Run pipeline with parameters

When pipeline successfully validated compiled YAML it will show the stage and jobs.

If validation failed for YAML it will show the error and won’t run the pipeline. For some cases if validation done but issues with security (permissions) — it create the pipeline but show the error message (while issue not resolve it will show fail status).

Stages view
Jobs view

Results can be different depending on your project, code and other. But for most of them will be shown the logs, warnings if available, job steps and even test results (if available) and even more — some of the extensions can add additional tabs with details like code analysis.

After running the job we can see the list of steps in the process with all logs and statuses for each step. So it can simple to look into each of the step with more details or even download raw log file.

Result with each step and logs
Environment selected for deployment

Images below provided the samples when QA environment required approval but user rejected it and it stop the pipeline and fail general status or success.

If pipeline failed this artifacts can not be used in the Releases. Only successful pipeline can be used with artifacts later. So it can be used in Releases to deploy to many environments.

QA deployment failed after Development is done (another sample foe multi-environment)

Next sample is where QA selected and approved by the user — successfully deployed.

QA deployment success

Of cause in different projects and with different configuration it can see the same results or different. But main idea is the same — need to go step by step and use documentation for more details ow to use and implement needed behavior.

3.5 Pipeline review

Here provided the short summary for the pipeline and visual structure of the used elements: Library, Environments.

Dependencies in Azure DevOps portal

The visual dependencies for files provided on the next image — here we can see what are stages here and jobs. All of them in the separate files.

Files in template

Also in the next image provided detailed view on the actual template for pipeline for building .NET applications.

Pipeline in details with steps

Hope this images will help you to have better vision on the pipelines and what’s happened inside.

Conclusion

Using Azure Pipelines is a great way to implement CI/CD to your projects and even simple. Here in the article we saw how can we implement the pipeline for .NET application (but it can be used for other types as well with changes in build template — artifacts is the actual results to be used) using a repository in Azure DevOps (Repos), configuration, usage templates, environments, libraries.

Additional topics here can be covered:

  1. Deploy Angular web application to Azure App Service with Azure Pipelines.
  2. Deploy application to multiple-environment.
  3. Use pipeline to build artifacts and classic releases to deploy to separate environments.
  4. Use Azure Pipelines to deploy resources on Azure (with Bicep).

Hope later I can cover all of them and provide the articles for each of them.

Links

  1. https://learn.microsoft.com/en-us/azure/app-service/
  2. https://learn.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops
  3. https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/pipelines-get-started?view=azure-devops
  4. https://learn.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops
  5. https://henriquesd.medium.com/deploying-an-angular-application-with-azure-pipelines-7c820116085f -sample for Angular web application built with Azure Pipeline.

--

--