There's no doubt that Cloud development is becoming more and more popular and for many, the de facto way of building, deploying and hosting apps on the web. But many of us still work on our day jobs in a cloud-less environment due to reasons that are not relevant to this post.

Fortunately, that's not an excuse to not get familiar with cloud development and take advantage of all the good things you can get from it. Azure offers a free account that includes a lot of services for free. Definitely, check the link above to find out more. If you want to get started with cloud development but are too afraid of spending your money on it, you got no excuses anymore :)

What we are going to do

In this post, I want to show you how to:

  • Create an App Service on Linux on Azure
  • Setting up a Build pipeline on Azure DevOps

Some requirements

To avoid making this post too long, I'll assume that you already have the following set up: (I'll give you links if you don't, so you can follow those before)

  • An account on Azure DevOps within an organization: Just follow the steps here

  • A git repo on Azure DevOps (previously known as VSTS) for your project: here and here

  • The actual code - An ASP.NET Core API up and running (the simplest initial template (ValuesController), either via VS or command line)

In the end, you should have your git repo configured both locally and on Azure DevOps so you're able to push/pull stuff. The "Repos" menu on Azure DevOps Looks like this:

Your Git repo on Azure DevOps

Creating the App Service on Azure

I'm also not going into too many details on how to create the App Service on Azure mainly because it was already explained better before. but I do want to show you via the Azure Portal briefly. So let's go:

  1. Log int into your Azure portal (after creating your free account)

  2. Go to: App Services > Add (+) > Web App > Create
    Adding a new Web Application via App Services

  3. Fill in all the fields, as seen here:
    Creating the App Service on Linux

  4. Hit Create!

Note: One thing about the Linux App Service: In the free account, you get always 10 App Services for free only if you use Windows and the Service Plan F1 as stated in the Pricing Page. Unfortunately, at the time of writing this, the Linux App Services is not available on F1 service plan so it's not free. BUT, during the first month of the free account, you get 170.00 in credits to spend. Also, the free account gives you in the first year Linux Virtual Machines.

Creating the build on Azure DevOps

Now that you have your git repo and your App Service created, it's time to configure the build pipeline on Azure DevOps. It's very easy and well integrated with ASP.NET Core, so we'll use most of the pre-defined values:

  1. Navigate to: Pipelines > + New > New build pipeline

  2. In the next page, you need to select the source for the build, which, in our case is Azure Repos Git. But as you can see you can connect to other Git providers like GitHub. Select your project and repository (it's probably already pre-selected) and hit Continue

Creating your first build on Azure DevOps

  1. On the "Select a template" page, select ASP.NET Core and hit Apply (you can also search for it)

  2. The next page is where we'll configure the build steps. Since we selected the ASP.NET Core template, Azure DevOps already gives us a set of pre-defined steps. I experimented with those a bit and came up with my own changes. I'll explain each step now:

Pipeline - Build pipeline

The first "box" is the one at the top of everything: Pipeline. There you can set up the name of the build pipeline, on which agent it runs and the projects to build/restore. Fill in the Build name and Agent "Hosted VS2017". Leave the rest as it is, we will change them later:

Configuring the initial build pipeline


The first step is the Restore. We don't have to do a restore since .NET Core 2.0, but in CI builds like this it's nice to have it separated mainly because we can measure how much time each step took.

Make sure to select the .NET Core version to 2.* The only thing we are going to change here is the "Path to project(s)". By default, it tries to restore all the projects. I like being very specific on each build step, so I'll change it to do the restore only for our simple API. You can of course just use the default configuration as well.

  1. In the "Path to project(s)" field, click on the "link" icon next to it and then on Unlink. This will enable us to change the field

  2. Change it to the path of your main API .csproj file. In my case, it's: **/StarWars.Api.csproj

Now it does a restore for the API project and if the API has dependencies to other projects in the solution, it will restore for those as well.


In the Build step, we will change 3 things. Again the "Path to project(s)", the "Arguments" and the "Working Directory".

  1. Click again in the "link" icon next to "Path to project(s)" and point it to the .csproj of your API again like in the restore step

  2. In the "Arguments" step add a --no-restore flag to avoid dotnet build doing a restore again: --configuration $(BuildConfiguration) --no-restore

  3. Expand the "Advanced" menu and add the folder of your API project to the Working Directory. In my case, this: src/StarWars.Api

I always use this format for my projects (and I believe most projects out there also use it):

My preference for project structure

You are not required to have this structure but I think it works nicely and it's well organized. For instance, I like to put all my test projects under a test folder in the same hierarchy as the src folder.


In the Test step, we are going to leave it as it is since probably there's no test in the project yet. But, what I also like to do is to specify the projects I want to test. Often, a big solution has multiple projects and probably you also have separate builds for each of them. In this case, it makes more sense to specify each test project to run instead of relying on a wildcard.


The publish step was the one I spent most of my time when I was first trying to set up my build pipeline. You can pretty much use the defaults and it will work just fine. The problem is: It publishes the artifacts with very weird names like this: Example of a not nice artifact name

Maybe the problem was me but I couldn't figure it out a better artifact name using the default options. I think we all like to have meaningful artifact names that contain stuff like the build number and so on. So that's what we are going to change.

  1. Unckeck the "Publish Web Projects" checkbox and add again the path to the API project: **/StarWars.Api.csproj

  2. In the "Arguments" paste this: -c $(BuildConfiguration) -o $(Build.StagingDirectory)/ci-build --no-build

The -c command will tell Azure DevOps to use the BuildConfiguration variable. By default the variable is set to release but you can change it in "Variables" tab on this page

The -o argument specifies the location of the published output. In my example, I told the pipeline to publish into the pre-defined Build.StagingDirectory variable plus into the ci-build folder. If you don't specify the ci-build folder here it will put the outputs of your build into a folder called "a" (Will be something like this: D:\a\1\a). I guess you can see why I specified a folder of my own. When things go south and you have to look at build logs, having something like "a" doesn't help too much (trust me, I suffered the pain).

And, of course, since we have built the project already, no need to do it again. So, the --no-build skips it

  1. I also unchecked the "Zip Published Projects" and "Add project name to publish path". We'll create our own zip in the next step

  2. Expand the "Advanced" menu and set the Working folder again as we did in the Build step

Should look like this at the end:
Publish step

Archive the publish output

Now that we have the outputs from our dotnet publish command, we need to archive it. Fortunately, Azure DevOps offers an archive phase step for us

  1. Click on the "+" button near the "Phase 1"

  2. Select the "Archive Files" option. You can search for it if not visible

  3. Choose a name for the step. E.g. "Archive Publish Output"

  4. In the "Root folder or file to archive" add the path to the publish output files configured before: $(Build.StagingDirectory)/ci-build

  5. I don't want to prepend the root folder name in the zip since I'd like my files to be at the root. So I unchecked the "Prepend root folder name to archive paths" checkbox

  6. In the "Archive file to create" is where we configure the name of the zip artifact. I changed mine to: $(Build.ArtifactStagingDirectory)/starwars-api.$(Build.BuildNumber).zip. Make sure also to mark the "Replace existing archive" just in case

Basically, this step will grab the files produced from the publish step available in $(Build.StagingDirectory)/ci-build, zip them with a name consisted of I used the Build.BuildNumber variable exposed by Azure DevOps to construct that name. Note that I also hard-coded the starwars-api part, but you can also set a variable for that, or use one of the many predefined ones like the build name, for instance.

The archive step should look like this:
Archive Step

Publish Artifact

In this step, we are just picking up the .zip file and moving it to the appropriate folder in Azure DevOps. Since we changed the defaults, we need to change some fields here as well to match our new artifact name/path:

  1. Change the "Path to publish" to: $(Build.ArtifactStagingDirectory)/starwars-api.$(Build.BuildNumber).zip. This will publish our zip to that location

  2. In the "Artifact name" field add again the artifact name we created: starwars-api.$(Build.BuildNumber)

  3. Leave the rest with default values

That's it for the build part! Now you can go to Pipelines > Builds > Queue, wait for it to finish and you should have a nice artifact :)

Example of a Meaningful build artifact

And if you click on it, you can download it to your machine:
Downloading our artifact

Coming up next, I'll explain how to create a Release Pipeline on Azure DevOps that consumes the artifact created in this post and deploy it to an App Service on Linux on Azure. So stay tuned :)