DEV Community

Anshul Jangale
Anshul Jangale

Posted on

1 1 1

Deploying a Frontend Application to Azure App Service with Bitbucket CI/CD Pipeline

Introduction

Deploying frontend applications to production environments can be challenging, especially when dealing with single-page applications (SPAs) like React. In this blog post, I'll walk through the process of setting up a complete CI/CD pipeline for deploying a frontend application to Azure App Service using Bitbucket Pipelines, and how to overcome common challenges like path mapping and URL rewriting.

Prerequisites

Before we begin, make sure you have:

  1. A Bitbucket repository with your frontend code
  2. An Azure subscription
  3. An Azure App Service plan and web app
  4. Service principal credentials for Azure deployment

Understanding the Pipeline Architecture

Our deployment pipeline follows these key steps:

  1. Install dependencies - Prepare the environment
  2. Parallel processes:
    • Build the application - Compile, bundle, and package the app
    • Security scan - Check for sensitive data in code
  3. Manual deployment trigger - Deploy to production with approval

The Bitbucket Pipeline Configuration

Here's our complete bitbucket-pipelines.yml file:

image: node:20

pipelines:
  branches:
    main:
    - step:
        name: install
        caches:
          - node
        script:
          - rm -rf node_modules package-lock.json
          - npm install --legacy-peer-deps 

    - parallel:
      - step:
          name: Build
          caches:
            - node
          script:
            - rm -rf node_modules package-lock.json
            - npm install --legacy-peer-deps
            - npm run build              
            - mv web.config dist/  # Move web.config inside dist/
            - apt update && apt install zip
            - zip -r app-$BITBUCKET_BUILD_NUMBER.zip dist package.json -x *.git* bitbucket-pipelines.yml
          artifacts:
            - "*.zip"
      - step:
          name: Security Scan
          script:
            # Run a security scan for sensitive data.
            - pipe: atlassian/git-secrets-scan:0.5.1            

    - step:
        name: Deploy to Production
        trigger: manual
        deployment: Production
        script:
          - pipe: atlassian/azure-web-apps-deploy:1.2.3
            variables:
              AZURE_APP_ID: $AZURE_APP_ID
              AZURE_PASSWORD: $AZURE_PASSWORD
              AZURE_TENANT_ID: $AZURE_TENANT_ID
              AZURE_RESOURCE_GROUP: $AZURE_RESOURCE_GROUP
              AZURE_APP_NAME: $AZURE_APP_NAME
              ZIP_FILE: 'app-$BITBUCKET_BUILD_NUMBER.zip'
              DEBUG: 'true'
Enter fullscreen mode Exit fullscreen mode

Let's break down each section:

Step 1: Installation

- step:
    name: install
    caches:
      - node
    script:
      - rm -rf node_modules package-lock.json
      - npm install --legacy-peer-deps 
Enter fullscreen mode Exit fullscreen mode

This step cleans any existing dependencies and installs fresh ones. The --legacy-peer-deps flag helps avoid compatibility issues between packages.

Step 2: Parallel Execution

- parallel:
  - step:
      name: Build
      caches:
        - node
      script:
        - rm -rf node_modules package-lock.json
        - npm install --legacy-peer-deps
        - npm run build              
        - mv web.config dist/  # Move web.config inside dist/
        - apt update && apt install zip
        - zip -r app-$BITBUCKET_BUILD_NUMBER.zip dist package.json -x *.git* bitbucket-pipelines.yml
      artifacts:
        - "*.zip"
  - step:
      name: Security Scan
      script:
        - pipe: atlassian/git-secrets-scan:0.5.1
Enter fullscreen mode Exit fullscreen mode

This section runs two processes in parallel:

  1. Build: Compiles the frontend application and packages it for deployment
  2. Security Scan: Checks the codebase for accidentally committed secrets or sensitive data

The build step is particularly important as it:

  • Builds the application
  • Moves the web.config file into the distribution folder
  • Creates a zip archive with a unique name based on the build number
  • Defines the zip file as an artifact to be used in later steps

Step 3: Deployment

- step:
    name: Deploy to Production
    trigger: manual
    deployment: Production
    script:
      - pipe: atlassian/azure-web-apps-deploy:1.2.3
        variables:
          AZURE_APP_ID: $AZURE_APP_ID
          AZURE_PASSWORD: $AZURE_PASSWORD
          AZURE_TENANT_ID: $AZURE_TENANT_ID
          AZURE_RESOURCE_GROUP: $AZURE_RESOURCE_GROUP
          AZURE_APP_NAME: $AZURE_APP_NAME
          ZIP_FILE: 'app-$BITBUCKET_BUILD_NUMBER.zip'
          DEBUG: 'true'
Enter fullscreen mode Exit fullscreen mode

This step:

  • Has a manual trigger for controlled deployments
  • Uses the Atlassian Azure Web Apps Deploy pipe
  • Passes necessary Azure credentials stored as repository variables
  • Specifies the zip file created in the build step

Configuring web.config for React SPA on Azure

One of the most challenging aspects of deploying a React SPA to Azure App Service is configuring URL rewriting correctly. Since SPAs use client-side routing, all routes need to be redirected to the index.html file.

Here's the web.config file we're using:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="React SPA" stopProcessing="true">
                    <match url=".*" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="/index.html" />
                </rule>
            </rules>
        </rewrite>
        <staticContent>
            <mimeMap fileExtension=".json" mimeType="application/json" />
            <mimeMap fileExtension=".js" mimeType="application/javascript" />
            <mimeMap fileExtension=".css" mimeType="text/css" />
            <mimeMap fileExtension=".ts" mimeType="application/javascript" />
            <mimeMap fileExtension=".tsx" mimeType="application/javascript" />
        </staticContent>
        <httpProtocol>
            <customHeaders>
                <add name="Access-Control-Allow-Origin" value="*" />
                <add name="X-Content-Type-Options" value="nosniff" />
            </customHeaders>
        </httpProtocol>
    </system.webServer>
</configuration>
Enter fullscreen mode Exit fullscreen mode

Understanding the web.config configuration

  1. URL Rewriting:

    • The rule matches all URLs (match url=".*")
    • It applies only when the requested URL doesn't match an existing file or directory
    • It redirects all such requests to /index.html, allowing the React router to handle them
  2. MIME Type Configuration:

    • Ensures proper MIME types for various file extensions
    • Critical for browsers to correctly interpret JavaScript, CSS, and JSON files
  3. HTTP Headers:

    • Access-Control-Allow-Origin: * - Enables cross-origin resource sharing
    • X-Content-Type-Options: nosniff - Prevents MIME type sniffing security vulnerabilities

Overcoming Common Challenges

Challenge 1: Path Mapping Differences in Azure

Azure App Service has a unique architecture that uses different virtual and physical path mappings, which can cause issues when deploying frontend applications:

Understanding Azure's Path Structure:

  • Physical Path: The actual location on the server where your files are stored (e.g., D:\home\site\wwwroot)
  • Virtual Path: The URL path that users access (e.g., https://yourapp.azurewebsites.net/path)

Common Issues:

  1. Root Path Configuration: By default, Azure deploys your application to the root of the App Service (/). If your frontend expects to be served from a subdirectory, path conflicts will occur.

  2. Virtual Directory Mapping: Azure allows you to map different physical folders to different virtual paths through the Azure Portal or configuration files.

Solution:

  1. In the Azure Portal, navigate to your App Service → Configuration → Path mappings
  2. Ensure your application files are correctly mapped to the appropriate virtual path
  3. For single-page applications, make sure the root directory (/) is correctly mapped to your application's distribution folder

Alternatively, you can define virtual applications and directories in your web.config:

<system.webServer>
    <virtualDirectory path="/" physicalPath="%SystemDrive%\home\site\wwwroot\dist" />
    <!-- Other configurations -->
</system.webServer>
Enter fullscreen mode Exit fullscreen mode

This ensures that when a user accesses the root URL, they're served content from the dist folder where your built frontend application resides.

Challenge 2: Web.config Configuration

The biggest challenge is often configuring the web.config file correctly for client-side routing.

Solution: Our web.config file above solves this by:

  1. Using the URL Rewrite module to redirect all non-file, non-directory requests to index.html
  2. Configuring proper MIME types for all static assets
  3. Setting appropriate HTTP headers for security and cross-origin requests

Setting Up Azure Environment Variables

For the deployment to work, you need to configure these repository variables in Bitbucket:

  • AZURE_APP_ID: Your Azure service principal ID
  • AZURE_PASSWORD: Your Azure service principal password
  • AZURE_TENANT_ID: Your Azure tenant ID
  • AZURE_RESOURCE_GROUP: The resource group containing your App Service
  • AZURE_APP_NAME: The name of your App Service

You can set these variables in Bitbucket by going to:
Repository settings > Repository variables

Testing the Deployment

After deploying, you should verify that:

  1. Your application loads correctly
  2. Deep linking works (you can navigate directly to any route)
  3. Static assets (images, CSS, JavaScript) load properly
  4. API calls work as expected

Conclusion

Deploying a frontend application to Azure App Service using Bitbucket Pipelines provides a robust and automated deployment workflow. The key challenges around path mapping and web.config configuration can be overcome with proper configuration.

By following this guide, you'll have a reliable CI/CD pipeline that builds, tests, and deploys your frontend application to Azure App Service, with proper routing for single-page applications.

Remember that the most critical parts are:

  1. The correct web.config configuration
  2. Proper understanding of Azure's virtual and physical path mapping
  3. Secure handling of Azure credentials

With these in place, your deployments should be smooth and reliable.

Hot sauce if you're wrong - web dev trivia for staff engineers

Hot sauce if you're wrong · web dev trivia for staff engineers (Chris vs Jeremy, Leet Heat S1.E4)

  • Shipping Fast: Test your knowledge of deployment strategies and techniques
  • Authentication: Prove you know your OAuth from your JWT
  • CSS: Demonstrate your styling expertise under pressure
  • Acronyms: Decode the alphabet soup of web development
  • Accessibility: Show your commitment to building for everyone

Contestants must answer rapid-fire questions across the full stack of modern web development. Get it right, earn points. Get it wrong? The spice level goes up!

Watch Video 🌶️🔥

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

AWS GenAI LIVE!

GenAI LIVE! is a dynamic live-streamed show exploring how AWS and our partners are helping organizations unlock real value with generative AI.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️