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

Nikita Kakuev - Sep 23 - - Dev Community

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: .
Enter fullscreen mode Exit fullscreen mode

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 like 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: .
Enter fullscreen mode Exit fullscreen mode

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! 😎

. . . . . . .
Terabox Video Player