Hosting a Pelican site on Azure Static Web Apps

So, earlier today I was looking at Twitter, and Scott Hanselman posted a silly thing about deploying modern web sites

Which I replied about how this blog is hosted, and what a mess it became when I wanted a custom domain name with HTTPS. He pointed out an Azure service I had missed previously.

And an Azure PM wanted to know how well it worked.

So, this post is about how well it works, written as I'm attempting to move this site over to Azure Static Web Apps.


First step: Creating the static web app, this is straightforward enough: Azure static site settings

I already have resource groups for my blog, so throw it in there, give it a name, although half the time with Azure, I don't know if the names have to be globally unique, or unique to the resource group, or what character limitations are. It's different for almost every service, but that's neither here nor there in this case. Next is the Github bits: Github settings for Azure

Mostly straightforward here too, although it's a bit annoying that you can't specify an existing workflow. But I can see how that would quickly get very complicated. It at least lets you preview it.

Go through the rest of the create process, and it only takes a few seconds to create the resource, which is nice. Creating the resource also creates a new workflow file in the chosen repository, with a somewhat random name. It would be nice to be able to set a name for this.

With the settings above, the generated Github action errors out, since it tries to copy resources from output, which doesn't exist since that's produced by Pelican after the build, and I have the whole directory excluded. The Jekyll Instructions mention that the first build will fail. As of the writing of this post (2020-11-25) The screenshots on most, if not all of the tutorials don't match the current create resource wizard. The "Build" tab is gone, and its contents are now under "Build Details".

Looking at the build logs:

Operation performed by Microsoft Oryx, https://github.com/Microsoft/Oryx
You can report issues at https://github.com/Microsoft/Oryx/issues

[...elided...]

Preparing output...

Copying files to destination directory '/bin/staticsites/ss-oryx/app'...
Done in 0 sec(s).

Removing existing manifest file
Creating a manifest file...
Manifest file created.

Done in 13 sec(s).


---End of Oryx build logs---
The app build failed to produce artifact folder: 'output'. Please ensure this property is configured correctly in your workflow file.

Oryx is a Microsoft build tool used for the App Service, and other things. It's smart enough to mostly do the right thing for common repository layouts. For example, it figured out that Python is needed to build this site, due to there being a requirements.txt file, created a venv, and ran pip install for the requirements. It would be nice if this tool was mentioned somewhere, unless the fact it uses Oryx is an implementation detail. The only thing I'm not entirely sure about with it is why it chooses Python 3.8.6 specifically. The particular Python version it uses seems to be settable from a runtime.txt file, which seems to be an ad-hoc standard by Heroku?

Once again, any mention of this in the actual documentation for static web apps would be nice. In addition to Python, there seems to be detection for dotnet, Hugo, Java, Node, PHP, and Ruby. The Build and workflow configuration mentions custom build commands, and implies that it always runs npm install and is very Javascript oriented. I've added app_build_command: invoke build to the with section on the build job. Let's see if it works as expected.

And it does not work as expected. Looking at the build logs:

App Directory Location: '/' was found.
[WARNING] Api Directory Location: 'api' could not be found. Azure Functions will not be created.
Starting to build app with Oryx
Azure Static Web Apps utilizes Oryx to build both static applications and Azure Functions. You can find more details on Oryx here: https://github.com/microsoft/Oryx
Oryx will build app with the following custom override command: invoke build
---Oryx build logs---

The build task is passing the build command to Oryx, which Oryx then seems to ignore, as the build fails in the same way. After falling down a too deep rabbit hole involving pulling the staticappsclient Docker image that the static-web-apps-deploy task uses to do deployment, the app_build_command sets the RUN_BUILD_COMMAND environment variable for Oryx, and the only build script generator in Oryx that uses that setting is the one for Node.

The workaround for now seems to be using a post-build command, like so:

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true
      - name: Check files
        run: "cat /home/runner/work/_actions/Azure/static-web-apps-deploy/v0.0.1-preview/Dockerfile && cat "
      - name: Build And Deploy
        id: builddeploy
        uses: Azure/static-web-apps-deploy@v0.0.1-preview
        with:
          azure_static_web_apps_api_token: ${{  }}
          repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
          action: "upload"
          verbose: true
          ###### Repository/Build Configurations - These values can be configured to match you app requirements. ######
          # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
          app_location: "/" # App source code path
          api_location: "api" # Api source code path - optional
          output_location: "output" # Built app content directory - optional
          ###### End of Repository/Build Configurations ######
        env:
          POST_BUILD_COMMAND: "invoke build"

There doesn't appear anything I can do to run a custom command in the Bash template Oryx uses for Python, and the POST_BUILD_COMMAND gets passed along to Oryx. invoke build is what I'm using to trigger the Pelican build (via the invoke package), but this could be replaced with whatever needs to be done to generate the static site.

With that done, there's now a copy of my blog on the nonsense host name generated by Azure, time to get a domain setup. As a test, instead of the main URL, I'm using ma.ttsieker.com, might as well get some use out of the silly vanity domain I bought the other week. This was simple enough Azure Static Site DNS Settings

It took a bit for it to pick up the new CNAME, but DNS is DNS. Overall, not too difficult to do, outside of figuring out how to get Oryx to run the command to build the site. I could have just kept the pip install wheel && pip install -r requirements.txt && invoke build that my current build script does, but Oryx will always run its build, and install requirements.txt and it just seems wasteful to do that twice.

Conclusions:

  • Overall easier than setting up a site hosted out of storage account, as there is no messing with CDNs.
  • The MSDN documentation needs to be updated to match the current deployment steps, but as it's still in Preview, it's understandable.
  • Mentioning Oryx in the documentation would be nice, and perhaps just make Oryx better known in general.
  • Having Azure create a commit that will make a failed CI build right when you create the site isn't a great experience. Maybe add a build command setting when creating the static app?
  • The builds take less time than the previous build script, mainly since it doesn't need to do a CDN purge. Excluding the CDN purge, it seems to take about the same amount of time.

social