Deploying Zola on Azure

2025-10-30

Writing about how a blog is wired together is a cliché, but it’s my turn now. At the time of writing, the blog is a static web page. No database queries or fancy server side rendering, just a simple file in response to a request.

But a static web page needs a place to live, and fortunately there are many options. I’ve tinkered with Azure the past months, so it was a short leap to try out Azure Static Web Apps.

To generate this site, I landed on Zola. Zola is a static site generator written in Rust and shipped as a binary. The latter is convenient, since I don’t need to set up a development environment in a programming language to get started. What was missing were instructions on how to get Zola up and running in Azure.

So here is how I wired up Zola to deploy on Azure Static Web Apps with GitHub Actions.

I started by creating an Azure resource group where my future blog should live. In the creation wizard, I authenticated with GitHub and selected the repo with my blog. I then weaked a few settings. Setting Build Presets to Custom and Deployment authorization policy set to GitHub.

After the web app was created, I navigated to the rather ugly URL that came with it and landed on a default Azure page.

Back at the GitHub repo, I noticed that Azure has added new GitHub Actions workflow which failed to run. Time to get the workflow up, running and green.

Previously, I selected the custom build preset for the web app. This gave us a workflow, but it didn't come with Zola preinstalled. The first step was to add Zola to the workflow.

- name: Install Zola
  uses: taiki-e/install-action@v2
  with:
    tool: zola@0.20.0

With Zola installed, I added a step to build the blog.

- name: Build
  run: zola build

After building with Zola, the static site ends up in the output folder. I updated the build and deploy step to only deploy the output directory.

- name: Deploy
  id: deploy
  uses: Azure/static-web-apps-deploy@v1
  with:
    azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_SALMON_WAVE_0C6840103 }}
    action: "upload"
    app_location: "/public"
    github_id_token: ${{ steps.idtoken.outputs.result }}
    skip_api_build: true
    skip_app_build: true

Below are all the steps in the modified build pipeline.

steps:
  - uses: actions/checkout@v3
    with:
      submodules: true
      lfs: false
  - name: Install OIDC Client from Core Package
    run: npm install @actions/core@1.6.0 @actions/http-client
  - name: Get Id Token
    uses: actions/github-script@v6
    id: idtoken
    with:
      script: |
          const coredemo = require('@actions/core')
          return await coredemo.getIDToken()
      result-encoding: string
  - name: Install Zola
    uses: taiki-e/install-action@v2
    with:
      tool: zola@0.20.0
  - name: Build
    run: zola build
  - name: Deploy
    id: deploy
    uses: Azure/static-web-apps-deploy@v1
    with:
      azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_SALMON_WAVE_0C6840103 }}
      action: "upload"
      app_location: "/public"
      github_id_token: ${{ steps.idtoken.outputs.result }}
      skip_api_build: true
      skip_app_build: true

After reloading the page, I finally had a working site that looked like a blog. All with just a few tweaks in the build pipeline. Changes pushed to the main branch are automatically deployed on Azure.

With a working site, it was time to run some scans. Since I’ve previously written about security headers, it would be bad not to practise it on my own blog. I ran a scan which resulted in a C. Room for improvement and more words in this post.

Looking at the missing headers, it’s no crisis since the blog is a static page. Still, it's good practice to tighten things where possible, so I added a staticwebapp.config.json with a few headers. I also prefer URLs with trailing slashes, so I added that to the config as well.

{
  "globalHeaders": {
    "Content-Security-Policy": "default-src 'self'; child-src 'none'; connect-src 'none'; font-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; script-src 'none'; style-src 'self' 'unsafe-inline'",
    "X-Frame-Options": "DENY",
    "Permissions-Policy": "camera=(), geolocation=(), microphone=(), payment=()"
  },
  "trailingSlash": "always"
}

I updated the build step so staticwebapp.config.json gets moved into the output folder, which lets Azure pick up the configuration and deploy it.

- name: Build
  run: |
    zola build
    cp staticwebapp.config.json public/staticwebapp.config.json

Then, it was time for a new scan of the security headers. I submitted the site again, and now it’s an A+.

Now I have a working blog built with Zola, which is deployed on Azure with proper security headers. All without too much work.

© 2025 Adrian Lamøy. Content licensed under CC BY-SA 4.0, unless otherwise noted.

No cookies served on this site. Enjoy your visit, banner-free.