How I cut the deployment time of an Astro (Node.js) site on Azure from 1 hour to ~3 mins

23 September 2024

Before:

Deployment on azure before

After:

Deployment on Azure after

The way Azure sets it up for you when you go through the steps of deploying a new Azure Web App service is objectively horrible. This is what Azure pushes to your repo:

# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy # More GitHub Actions for Azure: https://github.com/Azure/actions name: Build and deploy Node.js app to Azure Web App on: push: branches: - main workflow_dispatch: jobs: build: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - name: Set up Node.js version uses: actions/setup-node@v3 with: node-version: "20.x" - uses: pnpm/action-setup@v4 with: version: 9 - name: npm install, build, and test run: | npm install npm run build --if-present - name: Zip artifact for deployment run: zip release.zip ./* -r - name: Upload artifact for deployment job uses: actions/upload-artifact@v4 with: name: node-app path: release.zip deploy: runs-on: ubuntu-latest needs: build environment: name: "Production" url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} permissions: id-token: write #This is required for requesting the JWT steps: - name: Download artifact from build job uses: actions/download-artifact@v4 with: name: node-app - name: Unzip artifact for deployment run: unzip release.zip - name: Login to Azure uses: azure/login@v2 with: client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID }} tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID }} subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID }} - name: "Deploy to Azure Web App" id: deploy-to-webapp uses: azure/webapps-deploy@v3 with: app-name: "app-name" slot-name: "Production" package: .

So what’s happening here is that we

  1. Build the project in GitHub actions
  2. Zip it
  3. Send it to Azure
  4. Unzip it
  5. Run it

The problem is that we’re zipping it together with node_modules folder. So sending it and unzipping it takes a LOT of time. 🤯

I’m not sure why they recommend it this way, but after reading a bunch of poorly worded and outdated Azure guides and some short StackOverflow posts, I’ve figured out the way a Node.js app should be deployed.

Essentially we need to only send the code from GitHub to Azure and build right there. For that you don’t need to do any post-install scripts or anything, Azure App Service does it for you, here are the steps that it goes through as soon as the code reaches the service.

But it doesn’t do that by default, so you need to enable it. You do that by setting the environment variable SCM_DO_BUILD_DURING_DEPLOYMENT to true in Settings > Environment variables in Azure Portal.

Once we’ve done that, we now need to remove the build step from our GitHub Actions workflow and we’re done. Here is how the template looks now. 👇

# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy # More GitHub Actions for Azure: https://github.com/Azure/actions name: Build and deploy Node.js app to Azure Web App on: push: branches: - main workflow_dispatch: jobs: prepare: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v4 - name: Zip artifact for deployment run: zip release.zip ./* -r - name: Upload artifact for deployment job uses: actions/upload-artifact@v4 with: name: node-app path: release.zip deploy: runs-on: ubuntu-latest needs: prepare environment: name: "Production" url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} permissions: id-token: write #This is required for requesting the JWT steps: - name: Download artifact from build job uses: actions/download-artifact@v4 with: name: node-app - name: Unzip artifact for deployment run: unzip release.zip - name: Login to Azure uses: azure/login@v2 with: client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID }} tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID }} subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID }} - name: "Deploy to Azure Web App" id: deploy-to-webapp uses: azure/webapps-deploy@v3 with: app-name: "web-app" slot-name: "Production" package: .

There are also a few “gotchas” I’ve noticed when deploying to Azure Web App service:

  1. Forget yarn or pnpm. npm only. There is no way you can get those into App Service environment. And if there is, I really don’t think it’s worth the effort. Azure is using Kudu so you better know its ins and outs, I feel like, if you decide to install additional packages there.
  2. HOST=0.0.0.0 PORT=8080 before your start script, which in my (Astro) case was node ./dist/server/entry.mjs . You can’t pick the port. I tried. Didn’t work.
  3. Node version 20.14 is what it’s currently running in Azure as of the moment of writing this post. It was hard to find, they don’t print it in the dropdown when you select the environment when deploying the App Service. I got a few errors because the Node version wasn’t what my app was expecting 🤷‍♂️

And most important tip: upgrade your service to paid. The whole build and deploy process is unbearably slow on a free plan. But, frankly, that’s not an Azure specific issue, that’s most of PaaS nowadays, isn’t it?

Hope that helps! 😎

©2024 Rail Yard Works LTD is registered in England and Wales, no. 12126621.