Hendrik Bulens All about .NET development

Automatically deploy a static site to Azure App Service using Azure DevOps

Static websites are nothing to get excited about. To avoid being scorned, it’s probably best not to put it as a skill on your resume or to bring it up at a networking event. Even so, they are everywhere. And for good reason.

From a developer’s perspective, quite a lot has happened since the early days of the web. New technologies and frameworks have popped up like mushrooms. They have allowed us to quickly create powerful and beautiful web applications and web sites.

As far as I am concerned, the game-changer of the second half of the last decade was the rise of Angular and React as the two most popular frameworks. Before they became the de facto standard, I was still inexperienced and overwhelmed by the amount of options and the incredible pace at which frameworks surpassed others in popularity. Years later, React and Angular are still there and it’s fair to assume that Facebook and Google will keep on investing in their frameworks. This development has given me – and many others – some stability in a rapidly changing world. With the maturity of a technology inevitably comes initiative from the community. One such initiative I would like to zoom in on today concerns static documentation site generators.

Static website generators

I increasingly find myself creating static websites. It started last year when I rewrote the public documentation for one of the projects that I work on. It was such a nice experience that I decided to make some more for other areas, such as wikis, technical docs, and content pages for Teams.

The public documentation site I mentioned before is pretty big. It’s chock-a-block with guides, tutorials, manuals, etc. In terms of word count, we get close to a 250-page book. And it’s something that is updated almost daily. If we hadn’t had the tools to properly manage this, we would have had to hire an FTE just to maintain the website, while this is now only a small part of the developer’s job.

For this particular site, we use Docusaurus. Notwithstanding its dorky name, this is a rather excellent application. It was created by people at Facebook so it won’t come as a surprise that it’s built using React. It’s really easy to get started and the documentation is superb. It’s hard to make mistakes, and when you do, you have the tools to diagnose and debug the issue. With its second major version coming up, it’s getting better still. I’m not trying to sell Docusaurus but it’s definitely worth considering. Docusaurus focuses on documentation (which is why you write the docs in markdown files) but there are many candidates to choose from. Off the top of my head, I recall having investigated Read the Docs, GitBook, and ReadMe before I went with Docusaurus. Furthermore, there’s certainly no lack of static site generators in general. For instance, GatsbyJS and Hugo are pretty popular all-purpose frameworks.

To build a site with a static site generator, you’ll often have access to funky features such as hot module replacement. Once you are happy with the result and ready to deploy, you don’t need any of that in production. All you need is some HTML, JS, and CSS to host the website.

One thing these static site generators have in common is that the final product is a set of files that compose the static website. You could – and should – use CI pipelines to build the package. Eventually they will all call yarn build or something similar to that. While you’re at it, you may as well set up some CD pipelines to publish the website to complete the ‘from code to running web site’ cycle.

This is exactly what I will show you today. I will show you how to set up an Azure App Service and how you can publish a static (Docusaurus) website using Azure DevOps‘ CI/CD pipelines.

Generate the website

As I just mentioned, I will use Docusaurus to generate the documentation site. There are many others to choose from but they’ll more or less have the same methods of developing and deploying the site.

At the moment of writing, Docusaurus 1 is still in production while v2 is still in its alpha phase. I already have a website in production that uses v2 and it’s been reliable up until this point.

Head over to the docs‘ getting started section and you will find everything you need to hit the ground running. For starters, you’ll need Node.js and Yarn. If you don’t want to mess with your current Node.js version, then nvm will be your salvation. Once you’ve met the requirements, it’s just a matter of running the npx command as specified in the docs:

npx @docusaurus/init@next init static-site-azure-app-service classic

This will create all the files you need to start developing your website. It also contains some sample files so you can go ahead and fire up the web server without any additional steps. If you run yarn start in the ‘static-site-azure-app-service’ directory, the website will be ready on localhost:3000 in seconds.

From this point forward, it’s up to you to add content to the site. Let’s fast-forward to the moment that you’re ready to deploy the website to a test website. This could be as simple as running the yarn build command and uploading the contents of the build folder to a web server. But this is 2020, we don’t do that anymore.

Uploading files manually to a web server is a no-no

Setting up CI

The better option is to use the combo source control + CI/CD pipelines which automatically build and publish the website. This is not strictly necessary but in a professional context, this will basically be your default workflow.

The amount of setups is almost endless. You could use GitHub with GitHub pages, Azure DevOps with Azure Pipelines, GitHub with Azure Pipelines, Azure DevOps with Github pages, GitHub with CircleCI and surge.sh, etc.

We’re really spoiled for choice here. You need to do some research to select the right source control system, CI/CD pipeline, and static web hosting. For this post, I will use GitHub to store the code, Azure Pipelines to build and deploy, and Azure App Service as the host of the site.

To set up the pipeline in Azure DevOps, we first need to have a repository on GitHub. You could do the same as I did – add the project as a subdirectory – or use a separate repo. It doesn’t really matter, the result will be exactly the same. If all went well, your repository should look like this:

+-- blog
+-- docs
+-- src
+-- static
|   +-- img
+-- .gitignore
+-- README.md
+-- docusaurus.config.js
+-- package.json
+-- sidebars.js
+-- yarn.lock

Even though it is not strictly necessary for Azure DevOps, we will add a file called azure-pipelines.yaml. I’ll use this file to instruct Azure Pipelines how to build this project. It is possible to use the GUI to create a pipeline but I think this is a better approach as you don’t need to rely on a graphic user interface or even Azure DevOps. You could use the yaml file to build the project on CircleCI or any other CI platform.

Please pay attention to the workingDirectory and SourceFolder values. Azure Pipelines will check out the branch so you need to point to the directory as if you were on the root of the repository:

+-- src
| +-- static-site-azure-app-service
|     +-- package.json
|     +-- azure-pipelines.yaml

Now let’s head over to Azure DevOps. Go to your team collection and create a new pipeline. You should now see a wizard-style interface. Let’s go through it together:

  • Step 1: Connect
    • Select GitHub
  • Step 2: Select
    • Locate your repository
    • You may need to approve and install Azure Pipelines on this repository.
  • Step 3: Configure
    • Select ‘Existing Azure Pipelines YAML file’
    • A window pops up at the right-hand side of the screen.
      • In my case, I committed the file on the master branch.
      • The path dropdown list should display the azure-pipelines.yaml file in the subdirectory. Select it and continue.
  • Step 4: Review
    • Click Run to test the pipeline.

After a minute or so, you should have a working pipeline:

Nothing but green checks. Loving it!

If you look at the summary section of the pipeline, you notice there’s 1 published item:

Build summary

If you open the items in the drop folder, you should find an index.html, a docs directory, and a heap of JavaScript files. This is the static website that awaits to be deployed. Obviously, before we can deploy, we need something to deploy to.

Create Azure App Service

As I said earlier, we can deploy the website to pretty much anywhere. GitHub pages is extremely easy and Docusaurus has great support for Netlify, but I will go for an Azure App Service.

The same message applies to Azure as well: there are many ways to deploy a static app. Arguably the easiest option is to deploy to a static website on a blob service. During Build 2020, Microsoft announced the App Service Static Web App. This looks promising so this might be the way to go from now on.

But for now, I’ll create a simple App Service. Head over to the Azure Portal and create a new Web App resource. Here’s how I configured the web app:

Create a free web app

As you can see, we can use Node to run the web application but there’s no need for it in this case. Azure App Service comes with a lot of whistles and bells, most of which I don’t need for such a simple site. There’s one feature which is really interesting and which I’ll come back to in just a minute.

Proceed and create the resource. After a few seconds, you should have a running web app:

The web app is running!

Now that we have an environment to deploy to, let’s add CD to the mix. This should fill the gap between source control and the build package, and the Azure App Service.

Setting up CD

Sadly, Azure DevOps doesn’t allow us to use yaml syntax to define a release (yet – it’s been on the roadmap for a while). So we’ll have to work with the GUI instead. Go back to Azure DevOps and create a new release pipeline.

Add artifact

  • In the popup window, click ‘select with an empty job’.
  • In the artifacts area, click the ‘Add an artifact’ button.
  • In the popup window, select the CI pipeline you just created
  • Click Add
  • Click on the lightning icon and enable the continuous deployment trigger. This should put a little V icon next to the icon.

At this point, we have linked a release to a build pipeline and have set a trigger. Every time a commit is pushed to the repository, the build pipeline will build and drop the output, and subsequently gives the release pipeline the chance to do something with the output. That ‘something’ is defined in the release stages.

Add stage

  1. Click on the line below ‘Stage 1’ in the Stages area
  2. Click the + icon next to the agent job
  3. In the ‘Add tasks’ area, look for ‘Azure App Service Deploy‘ and add it.
  4. Select this task in the left agent job area.
  5. In the Azure subscription box, select the subscription which you used to create the App Service. If this is your first time, you may need to authorize this action.
  6. In the App Service Name box, select the App Service you just created.
  7. Change the package or folder. Click on the ellipsis and select the ‘drop’ directory.
  8. Save the pipeline
  9. Create a release

This might take a minute or so. You can monitor its status if you click on the release:

Monitoring the release

If the release has succeeded, you should have a functioning website on Azure:

The final result

The next time you commit and push your code, the build and release pipeline will be triggered automatically and your website will be updated within minutes. I don’t know about you but I find this highly satisfying.

Bonus: authentication

This was actually the main reason why I used an Azure App Service to host the technical docs for some of my projects. Without almost any effort, you can host your site in the cloud while it is only accessible by the people in the organization.

To configure authentication and authorization, look for the ‘Authentication/Authorization‘ menu in the Settings section.

Authentication the web app

Then it’s just a matter of configuring the identity providers.


This is just a basic example but it paves the way for more realistic setups.

It’s fairly unlikely that you want to update the production site on every developer’s commit. Instead, you should have a pipeline that does an update on every commit on a test site rather than a live site. In the meanwhile, another pipeline on the master branch should update the production site only. This way you have granular control what and when you update the site.

Knowing it is this easy, there should be no more excuses as to why the documentation is out of date or even absent.

Add comment

Leave a Reply

Hendrik Bulens All about .NET development