Azure Static Web Apps
1.) Setting Up Azure Tenant Resourcesâ
Creating the Static Web Appâ
âšī¸ NOTE: A GitHub admin with access to the FortisureIT GitHub account will need to temporarily disable Github IP protection during the creation of the static web app. This allows the resource to view the repository and create the link between the two.
-
Within Github:
- Enterprise Settings -> Authentication Security -> IP allow list -> Enable 'All IPs'
-
Navigate to the Azure tenant that you would like to create the static web app within. This may be the FortisureIT internal tenant, or an external tenant for a client depending on the use case.
-
Click Create a Resource at the top of the Azure tenant home page.
-
Search for Static Web App within the marketplace, and click create.
-
Within the Basics tab, fill out the project details, making sure to select the following under Deployment Details
- For Source use Github
- For Github Account use an account with access to the repository you plan to host on the static web app
- For Organization select Fortisure-IT
- For Repository select the relevant repository for your project
- For Branch select main/master
- If the repository/branch are not loading try refreshing the page. This is the step that requires the disabling of the Github IP protections.
-
Within the Deployment Configuration tab select the following
- For Deployment Authorization Policy select Github
-
Add the relevant tags to the resource and create.
â ī¸ NOTE: Remember to re-enable GitHub IP protection once the creation of the static web app resource is complete. 8. Within Github:
- Enterprise Settings -> Authentication Security -> IP allow list -> Disable 'All IPs'
2.) Setting Up Azure VM Resourcesâ
Repo-Level Runnerâ
With each new pipeline a new repo-specific runner should be added to the virtual machine to handle concurrent build actions for that repository.
âšī¸ NOTE: This section may need re-visited in the future to balance VM resource load vs storage load as additional runners take storage space.
NOTE: You must have, or rely on someone who has, admin access to the 'GitHub-Actions' Virtual Machine for this step.
- Log into the 'GitHub-Actions' virtual machine using the credentials for "GitHub Actions VM (Azure)" stored in Keeper.
- Instructions on how to log in are stored in the 'Notes' section of the credential in Keeper.
- Navigate to the repo-runners folder using
cd ./repo-runners.- Alternatively, the default runner is stored in 'root/actions-runner' if you need to access it.
- Create a new directory for your repository's runner using
mkdir ./[repo name]-runner. - Navigate into your new folder with
cd ./[repo name]-runner. - In a new browser instance, navigate to your GitHub repo and create a new runner via repo -> settings -> actions -> runners -> new-self-hosted-runner.
- Run the commands GitHub gives you in the terminal.
- Skip the first step
$ mkdir actions-runner; cd actions-runnersince you've already created and navigated to a custom directory. - When prompted for a tag make sure to add
repo-runnerso that workflow files using [self-hosted, repo-runner] call this runner instead of the org-level runner. - Skip the last step
runs-on: self-hostedsince we've already edited the workflow files.
- Skip the first step
- Once the runner is added, install GitHub Actions runner as a system service using
sudo ./svc.sh install. - Run the service using
sudo ./svc.sh start- The runner service will now start on boot and allow for background execution or concurrent actions.
- You can check the status of the runner with
sudo ./svc.sh status
- Copy the wipe_work script into our new repo runner directory by navigating into any other repo runner directory (such as fit-docs-runner for example) and executing cp ./wipe_work.sh ~/repo-runners/[repo name]-runner.
- This script will clean up any files left over after building
- Edit the script by navigating into your new repo runner directory, opening the file using nano or vim, and updating the TARGET_DIR to match your new directory.
- TARGET_DIR = "/home/GitHubAdmin/repo-runners/[repo name]-runner/_work"
- Make sure to save the file before exiting
- Grant permissions to the file by running the commands
sudo chown root:root ./wipe_work.shandsudo chmod 755 ./wipe_work.sh- These will require the GithubAdmin password
- Run
sudo visudoto edit the sudoers.tmp file, adding a new line for the new wipe_work.sh file in your directory. You can follow the format of the existing additions.- This will also require the GithubAdmin password
- ALL ALL=(root) NOPASSWD: /home/GitHubAdmin/repo-runners/[repo name]-runner/wipe_work.sh
- Make sure to save the file before exiting
3.) Updating the Workflow Fileâ
Navigate to the source GitHub repository. Note that there is now a GitHub workflow file under .github/workflows.
Workflowsâ
Creating the static web app in section 1 will automatically generate a default workflow file in the specified GitHub repo. Now, we need to update it to build on our internal Azure virtual machine, run our own build steps, and clean up after itself.
-
Update the build process from running on GitHub to running on our self-hosted virtual machine.
- Under each
runs-on:line, replaceubuntu-latestwith[self-hosted, repo-runner]. - This applies to every job in the file (
build_and_deploy,cleanup_runner, andclose_pull_request).
- Under each
-
Add explicit build steps to the
build_and_deployjob before the Azure deploy step.- Set up Node.js with
actions/setup-node@v4, matching the version your project uses. - Add
npm ciandnpm run buildfor both the root project and any nested project (such asapi). - On the
Azure/static-web-apps-deploy@v1step, setskip_app_build: trueandskip_api_build: trueso Azure does not try to rebuild.
- Set up Node.js with
-
Update the
Azure/static-web-apps-deploy@v1step's locations to match your project layout.- Set
app_locationto your built frontend folder (e.g.'dist'). - Set
api_locationto your API folder if applicable, otherwise leave as''.- Note: managed API functions are not supported on the free Static Web App tier â leave empty until upgraded.
- Set
output_locationto''since pre-built artifacts are being provided.
- Set
-
Add OIDC token retrieval for federated GitHub authentication in both
build_and_deployandclose_pull_request.- Install the OIDC client with
npm install @actions/core@1.6.0 @actions/http-client. - Use
actions/github-script@v6to callcoredemo.getIDToken()and capture the result. - Pass the token to the deploy step via
github_id_token: ${{ steps.idtoken.outputs.result }}.
- Install the OIDC client with
-
Add a
cleanup_runnerjob that runs thewipe_work.shscript set up in section 2.- Use
needs: build_and_deployandif: always()so it runs after every build, including failures. - The single step calls
sudo /home/GitHubAdmin/repo-runners/[repo name]-runner/wipe_work.sh.
- Use
âšī¸ NOTE: The exact name for secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_[GENERATED NAME] can be found within your repository settings on Github under Secrets and Variables -> Actions
âšī¸ NOTE: Below is an example version of the edited workflow file.
Example Workflow Fileâ
.github/workflows/azure-static-web-apps-[generated-name].yml
name: Azure Static Web Apps CI/CD
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
jobs:
build_and_deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: [self-hosted, repo-runner]
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
with:
submodules: true
lfs: false
clean: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Install root dependencies
run: npm ci
- 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: Build frontend
run: npm run build
- name: Install API dependencies
run: cd api && npm ci
- name: Build API
run: cd api && npm run build
- name: Deploy to Azure Static Web Apps
uses: Azure/static-web-apps-deploy@v1
id: builddeploy
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_[GENERATED NAME] }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: upload
app_location: 'dist'
api_location: '' #UPDATE TO 'api' ONCE OFF THE FREE STATIC WEB APP TIER, DOESNT SUPPORT MANAGED API FUNCTIONS
output_location: ''
github_id_token: ${{ steps.idtoken.outputs.result }}
skip_app_build: true
skip_api_build: true
- name: SHOW TESTING LINK
run: |
echo "Testing Environment Link ---> ${{ steps.builddeploy.outputs.static_web_app_url }}"
cleanup_runner:
runs-on: [self-hosted, repo-runner]
needs: build_and_deploy
if: always()
steps:
- name: Final cleanup
run: sudo /home/GitHubAdmin/repo-runners/[repo name]-runner/wipe_work.sh
close_pull_request:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: [self-hosted, repo-runner]
permissions:
id-token: write
contents: read
pull-requests: write
issues: write
steps:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- 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: Close pull request environment
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_[GENERATED NAME] }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
github_id_token: ${{ steps.idtoken.outputs.result }}
action: close
Use & Notesâ
Your CI/CD pipeline will run based on the workflow files and should automatically run when the trigger you specified takes place.
Monitoring & Debuggingâ
To monitor and debug your workflows, the 'actions' tab at the top of your GitHub repository can be viewed. Additionally, during and after a workflow has ran on a specific branch, the latest state of the workflow is shown after the name of the latest commit on the 'code' page of the GitHub repository for that branch.
Additional Functions on Workflows/Actionsâ
Workflow files are highly customizable and can be made to handle testing, add comments to PRs, create documentation, send notifications, manage tickets, and more. Further, what triggers them can be changed to many options. The guide above is designed to get a basic CI/CD pipeline in place, but adjustment and improvement is encouraged.
We currently do not have any documentation on improvements/additions to these flows.
âšī¸ NOTE: Documentation on any useful additions is more than welcome and should most likely go in a 'standards' or 'processes' directory on the docs site rather than in the tools section. However, adding a link to that directory/documentation here once it is created would be ideal.
GitHub Secret Key Environment-Level Security (Optional)â
As mentioned above, moving GitHub secrets from repository-level (Settings -> Secrets and variables -> Actions) to environment-level (Settings -> Environments -> Environment secrets) prevents developers with 'write' access to the repository from being able to change and delete the keys used by the workflows. Allowing changing of these keys could be dangerous as a developer could delete a key that is difficult to retrieve or, more impactfully, replace a key with their own value and cause the CI/CD pipeline to push to a different identity/web app entirely. These scenarios, however, would likely have to be intentional and malicious as it is not easy to accidentally access the secrets. So, moving secrets to the environment-level is best practice, but not functionally necessary.
Currently, we are unaware of an easy way to migrate keys to the environment-level since they cannot be read in GitHub. Likely the easiest/only way to achieve this is by recreating them one-by-one on the environment-level and regathering the values from Azure, then replacing their references in the workflow files.
FAQâ
How do I check workflow statuses and debug workflows?
- The 'actions' tab at the top of your GitHub repository can be used to see all current and past runs of workflows in your repository. This does
include all branches in the repository.
- Failed runs will be displayed and can be clicked to view more details on what exactly failed.
- Current runs can be clicked to view a real-time log of the process.
- Some or multiple steps of the process can be re-ran from here.
How do I restart or check the status of runners?
- To check the status of a runner on the 'GitHub Actions' virtual machine, first log into the VM using credentials in Keeper. Then navigate to the
directory of the runner you are trying to check or restart (likely either
cd actions-runnerfor the default orcd repo-runners/[repo name]-runnerfor a repository-specific runner).- To check the runner status use
sudo ./svc.sh status - To restart the runner use
sudo ./svc.sh stop; sudo ./svc.sh start
- To check the runner status use