In my previous blog I've showed you how to build an Azure deployable package for Ghost blog. It's now time to deploy that thing.

Goal

The goal of this build pipeline is to create an automatic release pipeline that will do the following:

  • Deploy the files to our App Service dev slot
  • Warm it up
  • Validate we got back an Http 200
  • Wait for manual approval (so we can do a manual sanity check)

When approved

  • Swap the dev slot with production
  • Validate we still got Http 200 on the production url

Create the Release pipeline

From Azure DevOps, create a new Release pipeline and make sure to select empty job

NewRelease

Add artifact

To link the previously build artifact package:

  • Click on Add artifact
  • Select Build as source type
  • Select the build pipeline we just created
  • Change the alias to drop
  • Click Add

AddArtifact-1

Stages

Next step is to create the stages, in our case: dev and prod. Start by creating these 2 stages empty, simply fill up their names and make sure they are linked together properly. You should have a line starting from the the artifacts box to dev and one from dev to prod. This means that if the previous step is successful, the pipeline will continue the deployment automatically, which is exactly what we want.

Stages

Variables

Before we create all the deployment tasks, let's start with the variables we going to need. This will save us some typing and it will also make our pipeline easier to understand and maintain over time.

  1. Click on Variables on the top navigation bar
  2. Create devSlotName, devSlotUrl and prodSlotUrl variables with your specific values
  3. Make sure to set the scope of those variables

Variables

Dev tasks

Alright, time to configure actual work. On the dev stage, create those tasks.

Stop Azure App Service

Funny things tend to happen when you try to change files on a server while another process is using them. Since I don't want to deal with this kind of situation, I always start by stopping completely my App Service. That way, I can avoid a whole aray of problems.

Be very careful though to only stop your dev slot and not the prod slot

Task type: Azure App Service manage

variables:
  devSlotName: 'dev'

steps:
- task: [email protected]
  displayName: 'Stop Azure App Service Dev Slot'
  inputs:
    azureSubscription: 'migblog-as-ghostblog - Azure'
    Action: 'Stop Azure App Service'
    WebAppName: 'migblog-as-ghostblog'
    SpecifySlotOrASE: true
    ResourceGroupName: migblog
    Slot: '$(devSlotName)'
Screenshot

StopAppService


Azure App Service deploy

This task deploys the actual package to the dev slot and start the Node.js server that runs Ghost blog.

Task type: Azure App Service deploy

variables:
  devSlotName: 'dev'

steps:
- task: [email protected]
  displayName: 'Deploy Azure App Service to Dev slot'
  inputs:
    azureSubscription: 'migblog-as-ghostblog - Azure'
    WebAppName: 'migblog-as-ghostblog'
    deployToSlotOrASE: true
    ResourceGroupName: migblog
    SlotName: '$(devSlotName)'
    packageForLinux: '$(System.DefaultWorkingDirectory)\**\*.zip'
    ScriptType: 'Inline Script'
    InlineScript: 'call npm install --only=prod --ignore-scripts'
    WebConfigParameters: '-Handler iisnode -NodeStartFile server.js -appType node'
    enableCustomDeployment: true
    ExcludeFilesFromAppDataFlag: false
Screenshot

Deploy1
Deploy2
Deploy3


Start Azure App Service

Start the Azure App Service on the dev slot

Task type: Azure App Service manage

variables:
  devSlotName: 'dev'

steps:
- task: [email protected]
  displayName: 'Start Azure App Service Dev slot'
  inputs:
    azureSubscription: 'migblog-as-ghostblog - Azure'
    Action: 'Start Azure App Service'
    WebAppName: 'migblog-as-ghostblog'
    SpecifySlotOrASE: true
    ResourceGroupName: migblog
    Slot: '$(devSlotName)'
Screenshot

StartSlot


Warm up slot

In order to avoid long loading time for the first clients hitting our blog, let's warm up the dev slot by sending a few web requests to it. This will force the web server to warm up and cache required files. In doing so, we'll also validate that the site is healthy after the blog update.

Task type: PowerShell

variables:
  devSlotUrl: 'https://dev.miguelbernard.com'

steps:
- powershell: |
   $statusCode = 0
   While($StatusCode -ne 200)
   {
     $res = wget $(devSlotUrl)
     $statusCode = $res.StatusCode
     Start-Sleep -Seconds 10
   }
  errorActionPreference: continue
  displayName: 'Warm up slot'
  timeoutInMinutes: 5

Screenshot

WarmUp


Recap of Dev tasks

The tasks of your dev stage should look like this
DevTasks

Prod tasks

At this point, we should be able to run this pipeline and validate that our dev slot is working properly. If everything is fine, we can now swap it with the prod slot.

Swap dev and prod

Task type: Azure App Service manage

variables:
  devSlotName: 'dev'

steps:
- task: [email protected]
  displayName: 'Swap Slots: migblog-as-ghostblog'
  inputs:
    azureSubscription: 'migblog-as-ghostblog - Azure'
    WebAppName: 'migblog-as-ghostblog'
    ResourceGroupName: migblog
    SourceSlot: '$(devSlotName)'
Screenshot

Swap


Validation

I'm a bit paranoid, so my last step is always to perform an extra validation directly in prod. That way if something goes wrong, I'll get a notification from Azure DevOps and I'll be able to swap back the environments.

Task type: PowerShell

variables:
  prodSlotUrl: 'https://blog.miguelbernard.com'

steps:
- powershell: |
   $statusCode = 0
   While($StatusCode -ne 200)
   {
     $res = wget $(prodSlotUrl)
     $statusCode = $res.StatusCode
     Start-Sleep -Seconds 10
   }
   
  errorActionPreference: continue
  displayName: 'Make sure site is up and running'
Screenshot

validate


Recap of Prod tasks

The tasks of your prod stage should look like this
ProdTasks

Pre-Approval

It's important to add a manual pre-approbation before running the production tasks. Otherwise, we won't have time to validate the dev environment before it's swapped with the prod one.

  1. Click on the little guy icon next to the prod stage. This is the Pre-Deployment configuration of that stage
  2. Enable Pre-Deployment approvals
  3. Add yourself as an approver

Approval

Automatic deployment

Wouldn't it be nice to kick start a deployment as soon as a new package is built? Easy peasy.

  1. Click on the little lightning bolt right by the drop artifact
  2. Enable Continous deployment trigger
  3. Voilà!

ContinousDeployment-1

Summary

You're all done! You now have a deployment pipeline that will automatically kick-off when a new package is available. It will prepare the dev environment and you'll receive a notification when it's ready to be validated and pushed to prod. All of this achieved with zero down time on your precious blog.