Scott Hanselman

Making a cleaner and more intentional azure-pipelines.yml for an ASP.NET Core Web App

March 04, 2020 Comment on this post [10] Posted in Azure | DotNetCore
Sponsored By

Azure Pipelines releasing to LinuxA few months back I moved my CI/CD (Continuous Integration/Continuous Development) to Azure DevOps for free. You get 1800 build minutes a month FREE and I'm not even close to using it with three occasionally-updated sites building on it.

It wasn't too hard, but as with all build pipelines you'll end up with a bunch of trial and error builds until you really get it dialed in.

I was working/pairing with Damian today because I wanted to get my git commit hashes and build ids embedded into the actual website so I could see exactly what commit is in production. How to do that will be the next post!

However, while tidying up we noticed some possible speed up and potential issues with my original azurepipeslines.yml file, so here's my new one!

NOTE: There's MANY ways to write one of these. For example, note that I'm allowing the "dotnet restore" to happen automatically as a sign effect of the call to dotnet build. Damian prefers to make that more explicit as its own task so he can see timing info for it. It's up to you, just know the side effects and measure!

Let's read the YAML and see what's up here.

  • My primary Git branch is called "main" so my Pipeline triggers on commits to main.
  • I'm using a VM from the pool that's the latest Ubuntu.
  • I'm doing a Release (not Debug) build and putting that value in a variable that I can use later in the pipeline.
  • I'm using a "runtime id" of linux-x64 and I'm storing that value also for use later. That's the .NET Core runtime I'm interested in.
  • I'm passing in the -r $(rid) to be absolutely clear about my intent at every step.
  • I want to build ONCE so I'm using --no-build on the publish command. It's likely not needed, but because I was using a rid on the build and then not using it later, my publish was wasting time by building again.
  • The dotnet test command uses -r for results (dumb) so I have to pass in --runtime if I want to pass in a rid. Again, likely not needed, but it's explicit.
  • I publish and name the artifact (fancy word for the resulting ZIP file) so it can be used later in the Deployment pipeline.

Here's the YAML


- main

vmImage: 'ubuntu-latest'

buildConfiguration: 'Release'
rid: 'linux-x64'

- task: UseDotNet@2
version: '3.1.x'
packageType: sdk

- task: DotNetCoreCLI@2
displayName: 'dotnet build $(buildConfiguration)'
command: 'build'
arguments: '-r $(rid) --configuration $(buildConfiguration) /p:SourceRevisionId=$(Build.SourceVersion)'

- task: DotNetCoreCLI@2
displayName: "Test"
command: test
projects: '**/*tests/*.csproj'
arguments: '--runtime $(rid) --configuration $(buildConfiguration)'

- task: DotNetCoreCLI@2
displayName: "Publish"
command: 'publish'
publishWebProjects: true
arguments: '-r $(rid) --no-build --configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: true

- task: PublishBuildArtifacts@1
displayName: "Upload Artifacts"
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: 'hanselminutes'

Did I miss anything? What are your best tips for a clean YAML file that you can use to build and deploy a .NET Web app?

Sponsor: This week's sponsor! This blog and my podcast has been a labor of love for over 18 years. Your sponsorship pays my hosting bills for both AND allows me to buy gadgets to review AND the occasional taco. Join me!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
March 10, 2020 2:02
Hey Scott,

When I'm building up my pipelines I prefer not to have the sdk version baked in. The global.json is the source of truth and I can reference it in the build pipeline. This way I don't have to change it in multiple places :)

So I have a global.json file which specifies the sdk version like so:

"sdk": {
"version": "3.1.102",
"rollForward": "latestPatch"

Then in the UseDotNet step in the pipeline looks like this:
- task: UseDotNet@2
displayName: 'Install .net core'
packageType: 'sdk'
useGlobalJson: true

March 10, 2020 6:22
Looks good to me, although my OCD'ish self is telling me that you could get rid of most, if not all, these double and single quotes.
They are unnecessary and distracting :P
March 10, 2020 8:09
Sometimes I set zipAfterPublish to false in the UseDotNet@2 task so I can clean-up a bit before creating a (zip) artifact. For example:

- task: DotNetCoreCLI@2
command: 'publish'
publishWebProjects: true
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: false

- task: DeleteFiles@1
SourceFolder: '$(Build.ArtifactStagingDirectory)/NowWebsite.Blazor/NowWebsite.Blazor/dist'
Contents: |

- task: ArchiveFiles@2
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/NowWebsite.Blazor/NowWebsite.Blazor/dist'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/nowwebsite.blazor.dist/nowwebsite.blazor.dist-$(Build.BuildId).zip'
replaceExistingArchive: true

- task: PublishBuildArtifacts@1
PathtoPublish: '$(Build.ArtifactStagingDirectory)\nowwebsite.blazor.dist'
ArtifactName: 'nowwebsite.blazor.dist'
publishLocation: 'Container'
March 10, 2020 8:19
I also like to upload test results, it'll provide details about failing test and stats about it.

And Most of the time I run test with docker-compose because I only mock dependencies that I can't run locally.
March 10, 2020 12:26
Am I the only one that doesn't like YAML? I know it's supposed to be more "human readable" than other formats, but it just feels so loose and fragile that I almost get anxiety just looking at it...
March 10, 2020 17:37
Matt, thanks for that tip about using the SDK from the global.json file. That helped clean up my build script.
March 10, 2020 19:35
Good starter. You can combine your build and publish steps into one and that will save you a few ticks on your pipeline tasks.

For a system where you want to then deploy your application then you'll have actual defined environments or at least prod / nonprod. This pushes you into multistage land where you're going to be adding Azure Pipeline Environments, stages to your yml file and then have the meat of your deployment in a template yml file.
March 14, 2020 14:16
Thanks, Scott. This is great because it is so simple. I had no idea the SourceRevisionId property exists, which makes me wonder what other useful properties I need to learn. Google leads me here when I search for "msbuild SourceRevisionId", but where can I find a comprehensive list?
March 14, 2020 20:31
Is it just me thats confused whether the deployment is supposed to be part of my azure-pipelines.yml file?

The Pipelines UI seems to work much better with them left out and put into releases, but I can't see how to commit releases to git. The documentation seems to suggest that we should be using Deployment Jobs in the azure-pipelines.yaml file but then what is the whole releases thing for?
March 18, 2020 7:50
@Jonathan Marston nope, I feel the same, don't really enjoy it at all. I don't think the world needed a new descriptive "language" at all, and YAML for me seems pretty poorly thought out. It works, and is being force-fed so I have to use it, but man I wish it would go away...

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.