AppVeyor – Continuous Delivery to Azure for your .NET Core Applications

Today’s header image was created by Ollie Tigwell at Unsplash
This week we’re going to continue where we left off with AppVeyor.
if you haven’t read my previous post on AppVeyor, I’d recommend giving it the once over.
As with last time, this week’s topic isn’t necessarily .NET Core specific, but it seems right to finish what we started. Plus, how cool would it be for the latest version of a repository to be built, tested, and deployed for us as soon as we commit to it?
Before we do that though, I want to point out that I’ll be using AppVeyor to publish to Azure. If you’re not using Azure to host your .NET Core applications, then parts of this blog article wont apply to your used case. I’ll point out where things don’t apply, as we go along.
As with last time, we’ll cover a little software Engineering theory first.
I know it’s boring, but some people don’t know this stuff. If you already know about this stuff, then you can skip to the gooey goodness by clicking here
Continuous Delivery
Continuous delivery (CD) is a software engineering approach in which teams produce software in short cycles, ensuring that the software can be reliably released at any time. It aims at building, testing, and releasing software faster and more frequently. The approach helps reduce the cost, time, and risk of delivering changes by allowing for more incremental updates to applications in production. A straightforward and repeatable deployment process is important for continuous delivery
A lot of people confuse Continuous Delivery with Continuous Deployment. The latter (Continuous Deployment) is all about getting the very latest changes out to the customer as soon as they are committed to the code base.
It’s not a perfect analogy, but you can think of Visual Studio Code Insiders as software which follows Continuous Deployment, as it’s an almost nightly build of the VS Code GitHub repo with the latest features and bug fixes.
it’s not a perfect analogy, because it doesn’t really work like that. But it’s close enough for now.
Whereas Continuous Delivery is all about having a specific delivery pipeline which facilitates testing and approval of all changes before a build can be used in production.
A basic pipeline might be as follows:

A single code change (or a collection of code changes) will follow the path from Version Control through to Release, and can be stopped along the way at any point. A failing unit test (the first red box in the above diagram) feeds back to the delivery team, who implement a fix for the failing test. This loop of triggers and feeding back continues until the code reaches the Release stage (this, typically, is when the code is handed over to the customer).
CD for .NET Core
If you remember from last time,
you did read the previous article in this series, right?
we got AppVeyor to clone and build a repository whenever a push to the master branch happened.
This time, we’ll build a separate script to do the same thing (build, test, and bundle our application) but with the included step of pushing it to our production environment. For dwCheckApi this means pushing to Azure.
quick refresher: the latest build of dwCheckApi is available at dwcheckapi.azurewebsites.net and if you want to fork that repo and play along, you can do so here
Why the Separate AppVeyor script?
Think of it this way: the AppVeyor script that we built last week uses the master branch. But what if we want that to use a development (or feature) branch for CI? If we had one script which built, tested, packaged and deployed our application, then we’d need to either stop it from doing the final step or create a separate script just for CI.
oh wait we don’t need to, because we did that last time.
Having a separate script for deploying to production makes sense; if only to separate our development builds from the master build.
I know that (at the time of writing) dwCheckApi only has one branch, but it might have more in the future. Plus, whatever you’re using might have multiple branches
Building a Deployment Script
The first thing we need to do is get the latest version of the code from our repository. I’ll leave you to figure out how to do that, as your repo might be different from mine.
unless you’ve forked dwCheckApi
Once you’ve done that, you’ll need to create a new script for building, testing and deploying.

I’ve called mine appveyor-deploy.yml, but you can call yours whatever you wish.
We’ll build the script content to match most of the steps in in the script we built last time. It would be pretty strange to have a deploy script which differed completely from the CI build script, unless there was a specific reason to have it differ. I can’t think of one, so let’s base it on that.
Here’s the deploy script that we’ll create:
version: 1.1.{build} | |
image: Visual Studio 2017 | |
branches: | |
only: | |
- master | |
init: | |
- git config --global core.autocrlf true | |
before_build: | |
# Display minimal restore text | |
- cmd: dotnet restore ./src/src.csproj --verbosity m | |
build_script: | |
# output will be in ./src/bin/debug/netcoreapp1.1/publish | |
- cmd: dotnet publish ./src/src.csproj | |
artifacts: | |
- path: '\src\bin\Debug\netcoreapp1.1\publish' | |
name: WebSite | |
type: WebDeployPackage | |
test_script: | |
# tests in here | |
- cmd: dotnet restore ./tests/tests.csproj --verbosity m | |
- cmd: cd tests | |
- cmd: dotnet xunit | |
deploy: | |
- provider: Environment | |
name: dwCheckApi-Production |
Go ahead and paste that code in. We’ll give it a read through in a moment.
The Deploy Script Proper
Taking a look at the deploy script and comparing it to the build script, they look quite similar.
Here’s the build script from last time, just as a refresher:
version: '1.0.{build}' | |
image: Visual Studio 2017 | |
branches: | |
only: | |
- master | |
init: | |
# Good practise, because Windows line endings are different from Unix/Linux ones | |
- cmd: git config --global core.autocrlf true | |
install: | |
# Install repo specific stuff here | |
before_build: | |
# Display .NET Core version | |
- cmd: dotnet --version | |
# Display minimal restore text | |
- cmd: dotnet restore ./src/src.csproj --verbosity m | |
build_script: | |
# output will be in ./src/bin/debug/netcoreapp1.1/publish | |
- cmd: dotnet publish ./src/src.csproj | |
after_build: | |
# For once the build has completed | |
artifacts: | |
- path: '\src\bin\Debug\netcoreapp1.1\publish' | |
name: WebSite | |
type: WebDeployPackage | |
clone_depth: 1 | |
test_script: | |
# restore packages for our unit tests | |
- cmd: dotnet restore ./tests/tests.csproj --verbosity m | |
# run the unit tests (requires changing into the test directory) | |
- cmd: cd tests | |
- cmd: dotnet xunit | |
on_finish : | |
# any cleanup in here | |
deploy: off |
Aside from where we’ve removed comments and empty sections, we’ve added the following section:
deploy: | |
- provider: Environment | |
name: dwCheckApi-Production |
Environments
Before we commit our changes, we need to create an environment. I guess we should figure out what an environment is, too.
Environments are an AppVeyor feature which allow us to deploy our build artifacts to an external hosting environment.
Ok, I used the word “environment” way too many times, there
We’ll use an (AppVeyor) environment to store all of the settings that we need to asynchronously deploy a built, tested and packaged application. So let’s go create one.
Head over to the Environments tab in the AppVeyor UI
click here to go directly to the environments tab
then click “New Environment” and you should see something similar to this:
We need to pick a provider which will line up with our chosen hosting environment. As dwCheckApi is hosted on Azure, we can use the “Web Deploy” option.
this is specific to Azure, obviously
As soon as you choose “Web Deploy”, the screen will fill up with all sorts of options:

I know what you’re thinking,
was that five shots or six?
I was a little overwhelmed when I saw all of those options, too.
Caveat
The first thing we need to do is make sure (and this is a pitfall I fell down) that the new Environment uses the name that we provided in the deploy script.
deploy: | |
- provider: Environment | |
name: dwCheckApi-Production |
AppVeyor will use the value we supply in the name field to pick the correct Environment to use when deploying. So make sure that whatever you entered in the name field in the deploy section
I chose “dwCheckApi-Production” for mine, but you can call yours whatever you like
matches the name of the Environment we’re about to create. Otherwise you’ll end up with deploys that don’t run to completion.
Web Deploy Settings
So, what do we need to enter into the Web Deploy settings? dwCheckApi is published on Azure, so we need to get some settings from there.
I say “we”
I’m going to assume that you’re hosting is being provided by Azure; if not, then you’ll need to find similar settings from your chosen hosting provider.
they probably already provide them, to be honest. Otherwise, how have you published to that environment already?
In any case, head over to Azure and load the App Service blade for your… well, app service:
Once the App Service blade has loaded, click on “Get Publish Profile”
I intentionally left the mouse pointer there in the image
and this will download a file called “<your app service name here>.PublishSettings”, which is an XML-like file.
psst, mine was called “dwCheckApi.PublishSettings”. Not that you needed to know that, mind
In here, you’ll find the values for the Environment settings. Here is an edited version of a publish profile, and we’ll use it to fill in the Environment settings in a moment:
<publishData> | |
<publishProfile | |
profileName="dwCheckApi - Web Deploy" | |
publishMethod="MSDeploy" | |
publishUrl="dwcheckapi.someUrl.someService.tld:443" | |
msdeploySite="dwCheckApi" | |
userName="$dwCheckApi" | |
userPWD="CorrectHorseBatteryStaple" | |
destinationAppUrl="http://dwcheckapi.azurewebsites.net" | |
SQLServerDBConnectionString="" | |
mySQLDBConnectionString="" | |
hostingProviderForumLink="" | |
controlPanelLink="http://panel.someService.tld" | |
webSystem="WebSites"> | |
<databases /> | |
</publishProfile> | |
</publishData> |
that’s not my real publish profile, obviously. I’ve changed the values of all of the sensitive fields.
Firstly, we need the server name. We’ll use the publish URL as a base, as we need to add some things to it:
publishUrl="dwcheckapi.someUrl.someService.tld:443" |
We need to add “/msDeploy.axd?site=” then whatever the value of “msdeploySite” is. Using the values from publish profile above, we’ll end up with:
dwcheckapi.someUrl.someService.tld:443/msdeploy.axd?site=dwCheckApi |
We added “msDeploy.axd” because of the value of the publish method:
publishMethod="MSDeploy" |
If your publish method field has a different value, then I would recommend checking this page in the AppVeyor documentation.
The value we just created needs to go into the Server field of the Environment settings.
The value for the Website Name setting is taken from the following field in the publish profile:
msdeploySite="dwCheckApi" |
The value for the User Name setting comes from the aptly named “userName” field of the publish profile:
userName="$dwCheckApi" |
The value for the Password setting comes from the “userPWD” field:
userPWD="CorrectHorseBatteryStaple" |
no, this isn’t the password for my publish profile
and will be encrypted by AppVeyor when we hit save.
The value for the “Artifact to deploy” field comes from the atrifacts section of our deploy script:
artifacts: | |
- path: '\src\bin\Debug\netcoreapp1.1\publish' | |
name: WebSite | |
type: WebDeployPackage |
We instructed AppVeyor to build an artifact called “WebSite”, so that’s what we’ll use for our publish Environment.
Make sure the check the following checkboxes:
- ASP.NET Core application
- Force restarting ASP.NET Core application on deploy
which will appear after you check “ASP.NET Core application”
- Take ASP.NET application offline during deployment
Then click “Add Environment”
New Project
Now that we’ve added an environment, we need to add a new project which will use that new environment in conjunction with our appveyor-deploy.yml file to deploy everything for us.
Head back to the projects page in AppVeyor
or click this link
and click on “New Project”
just like we did when setting up our original project, last time
Select the repository we’re going to deploy from the list.

don’t worry that we’re adding a duplicate project just yet, we’re about to change the name of it
Once the project has been added, we’ll be taken to the Settings page for that project. We really only need to change a few settings. First, give it a suitable name as it will have defaulted to the name of the repository
I chose “dwCheckApi-Deploy” for mine, but you can choose whatever you like
then scroll down to “Custom configuration .yml file name” and enter appveyor-deploy.yml (which is the name of our deployment yml). then click “Save”.
Here is what my deploy project looks like:

Once that has been saved, all that remains it to commit your appveyor-deploy.yml file and watch the build and deploy happen.
Caveat
At the time of writing, dwCheckApi only has one branch (i.e. master). When I committed my appveyor-deploy.yml to that branch, both projects in AppVeyor fired, which meant that I had a separate build and deploy of my application (one get’s queued whilst the other is processed).
This is fine for me, but you might want to revisit your appveyor.yml file (i.e. the non-deploy file) ensuring that it builds the correct branch for your circumstances.
Deploy
Once the deploy project has started, we can head to the project screen and see the build, test, and deploy steps in progress; it should look a little like this:

And heading over to your hosting environment will show the new version of the code.
in my case, heading to dwcheckapi.azurewebsites.net
et voilà
.NET Core And Azure Specific Caveat
In our environment settings, we instructed AppVeyor to take our application offline while deploying and restart it once it has finished deploying. While AppVeyor is doing it’s magic
and by that, I mean deploying the latest build
and for a short while after
depending on your application’s startup tasks
you (or your users/customers) might not be able to access the application – any requests for the application will receive the app_offline.html page whilst the application is restarting.
this can be customised by adding an “app_offline.html” file into the “www_root” directory and adding the static files service in your startup class.
Just something to bear in mind.
Conclusion
We’ve taken a pre-existing AppVeyor project and added a deployment step to it.
Ok, technically we’ve added a separate project for deployment, but I prefer to do that. That way, you’re only deploying changes to a specific branch.
even though the demo repository that I chose only has one branch, but you should be able to see where this is useful
Have you built, tested, and deployed anything with AppVeyor yet? If not, why not? It’s really simple to get going, and a nice alternative to things like Octopus Deploy.