Static Website to GitHub and Azure#

Use this workflow to start a new static website on a Mac, create a GitHub repository, push the code, and deploy it to Azure from GitHub.

Trigger#

Use this when starting a small website, documentation site, landing page, or static frontend that should be hosted online.

Inputs#

Before starting, have:

  • A Mac with Terminal access
  • A GitHub account
  • An Azure account
  • A project name
  • A rough idea of the first page

Recommended tools:

  • Homebrew
  • Git
  • GitHub CLI
  • Azure CLI
  • Node.js, if using a build tool like Vite, Astro, Hugo, or Next static export

1. Choose names#

Pick names before creating anything.

PROJECT_NAME="my-static-site"
GITHUB_USER="your-github-username"
AZURE_RESOURCE_GROUP="rg-my-static-site"
AZURE_LOCATION="westus2"
AZURE_STATIC_APP_NAME="swa-my-static-site"

Use lowercase names with hyphens. Avoid spaces.

2. Prepare the Mac#

Install Homebrew if it is not already installed:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Install the basic tools:

brew install git gh azure-cli

Check the installs:

git --version
gh --version
az version

Sign in:

gh auth login
az login

3. Create the website folder#

Create a workspace and project folder:

mkdir -p ~/Projects
cd ~/Projects
mkdir "$PROJECT_NAME"
cd "$PROJECT_NAME"

4. Create a simple static site#

For a plain HTML site, create these files.

cat > index.html <<'EOF'
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>My Static Site</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main>
      <h1>My Static Site</h1>
      <p>Hello from a static website deployed with GitHub and Azure.</p>
    </main>
  </body>
</html>
EOF

cat > styles.css <<'EOF'
body {
  margin: 0;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: #f7f7f8;
  color: #1f2937;
}

main {
  max-width: 720px;
  margin: 10vh auto;
  padding: 24px;
}
EOF

If using a framework, create the site with that tool instead. For example, Vite:

npm create vite@latest "$PROJECT_NAME"
cd "$PROJECT_NAME"
npm install
npm run build

For Vite, the final build folder is usually dist.

5. Test locally#

For a plain HTML site:

python3 -m http.server 5173

Open:

http://localhost:5173

For a Node-based project, use the project command:

npm run dev

Stop the local server with Control-C.

6. Initialize git#

Create a .gitignore:

cat > .gitignore <<'EOF'
.DS_Store
node_modules/
dist/
.env
.env.local
EOF

Initialize the repository:

git init
git add .
git commit -m "Initial static website"
git branch -M main

7. Create the GitHub repository#

Create a new GitHub repo and push the code:

gh repo create "$PROJECT_NAME" --private --source=. --remote=origin --push

Use --public instead of --private if the site repo should be public.

Verify:

gh repo view --web
git status

8. Create Azure resources#

Create a resource group:

az group create \
  --name "$AZURE_RESOURCE_GROUP" \
  --location "$AZURE_LOCATION"

Create the Azure Static Web App connected to GitHub:

az staticwebapp create \
  --name "$AZURE_STATIC_APP_NAME" \
  --resource-group "$AZURE_RESOURCE_GROUP" \
  --source "https://github.com/$GITHUB_USER/$PROJECT_NAME" \
  --branch main \
  --location "$AZURE_LOCATION" \
  --app-location "/" \
  --login-with-github

For a framework build, set the output location when Azure asks or update the generated GitHub Actions file later.

Common output locations:

Project typeApp locationOutput location
Plain HTML/leave blank
Vite/dist
Astro/dist
Hugo/public

9. Check the GitHub Actions deployment#

Azure should create a GitHub Actions workflow in the repo.

Check the latest run:

gh run list

Watch the deployment:

gh run watch

If the workflow file was created remotely by Azure, pull it locally:

git pull

10. Find the live Azure URL#

Get the deployed site URL:

az staticwebapp show \
  --name "$AZURE_STATIC_APP_NAME" \
  --resource-group "$AZURE_RESOURCE_GROUP" \
  --query "defaultHostname" \
  --output tsv

Open:

https://PASTE-THE-HOSTNAME-HERE

11. Make future updates#

Edit files locally, then commit and push:

git status
git add .
git commit -m "Update website"
git push

GitHub Actions should deploy the update automatically.

Watch it:

gh run watch

Decision points#

DecisionOption AOption BWhen to choose
Repository visibilityPrivatePublicUse private for personal drafts or client work
Site typePlain HTMLFrameworkUse a framework when the site needs components, routing, or build steps
Deploy methodAzure Static Web AppsAzure Storage static websiteUse Static Web Apps when deploying from GitHub Actions
Regionwestus2Another Azure regionChoose a region near your users or existing resources

Review#

Before calling the workflow complete, confirm:

  • The site runs locally
  • The code is committed
  • The GitHub repository exists
  • GitHub Actions has a successful deployment run
  • Azure Static Web Apps shows a live hostname
  • The live URL loads in a browser
  • The generated workflow file is pulled into the local repo

Troubleshooting#

ProblemFix
gh is not authenticatedRun gh auth login
Azure login failsRun az login again and choose the correct subscription
Wrong Azure subscriptionRun az account list --output table, then az account set --subscription "SUBSCRIPTION_NAME_OR_ID"
GitHub Actions cannot find the build outputCheck app_location and output_location in .github/workflows/
Plain HTML deploy tries to run a buildLeave output location blank and keep app location as /
Site URL shows an old versionCheck the latest GitHub Actions run and wait for deployment to finish
Local git push failsRun gh auth status, confirm the remote with git remote -v, then retry

Output#

At the end, you should have:

  • A local website folder on the Mac
  • A GitHub repository with the site code
  • A GitHub Actions deployment workflow
  • An Azure Static Web App
  • A live public URL

Notes#

Save these after each project:

  • GitHub repo URL:
  • Azure resource group:
  • Azure Static Web App name:
  • Live site URL:
  • Framework used:
  • Build output folder:
  • Date deployed:

Script#

#!/usr/bin/env bash
set -euo pipefail

# ---------------------------------------------------------------------------
# create-swa.sh — provision an Azure Static Web App and print the deploy token
#
# Usage:
#   ./scripts/create-swa.sh [OPTIONS]
#
# Options:
#   -n, --name          Static Web App name          (required)
#   -g, --resource-group  Resource group name        (default: <name>-rg)
#   -l, --location      Azure region                 (default: eastus2)
#   -s, --sku           Free | Standard              (default: Free)
#   -h, --help          Show this help
#
# Examples:
#   ./scripts/create-swa.sh --name my-docs-site
#   ./scripts/create-swa.sh -n my-docs-site -g my-rg -l westus2 -s Standard
# ---------------------------------------------------------------------------

usage() {
  sed -n '/^# Usage:/,/^# ---/p' "$0" | sed 's/^# //' | sed 's/^#//'
  exit 0
}

# ── defaults ────────────────────────────────────────────────────────────────
APP_NAME="repeat-until-new"
RESOURCE_GROUP="repeat-until-new-rg"
LOCATION="eastus2"
SKU="Free"

# ── parse args ───────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
  case "$1" in
    -n|--name)           APP_NAME="$2";        shift 2 ;;
    -g|--resource-group) RESOURCE_GROUP="$2";  shift 2 ;;
    -l|--location)       LOCATION="$2";        shift 2 ;;
    -s|--sku)            SKU="$2";             shift 2 ;;
    -h|--help)           usage ;;
    *) echo "Unknown option: $1" >&2; usage ;;
  esac
done

if [[ -z "$APP_NAME" ]]; then
  echo "Error: --name is required" >&2
  usage
fi

# Default resource group to <name>-rg if not provided
RESOURCE_GROUP="${RESOURCE_GROUP:-${APP_NAME}-rg}"

# ── preflight ────────────────────────────────────────────────────────────────
if ! az account show &>/dev/null; then
  echo "Not logged in to Azure. Running: az login"
  az login
fi

SUBSCRIPTION=$(az account show --query "name" -o tsv)
echo ""
echo "Subscription : $SUBSCRIPTION"
echo "App name     : $APP_NAME"
echo "Resource group: $RESOURCE_GROUP"
echo "Location     : $LOCATION"
echo "SKU          : $SKU"
echo ""

read -r -p "Proceed? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }

# ── resource group ───────────────────────────────────────────────────────────
if az group show --name "$RESOURCE_GROUP" &>/dev/null; then
  echo "Resource group '$RESOURCE_GROUP' already exists — skipping creation."
else
  echo "Creating resource group '$RESOURCE_GROUP' in $LOCATION..."
  az group create --name "$RESOURCE_GROUP" --location "$LOCATION" --output none
fi

# ── static web app ───────────────────────────────────────────────────────────
if az staticwebapp show --name "$APP_NAME" --resource-group "$RESOURCE_GROUP" &>/dev/null; then
  echo "Static Web App '$APP_NAME' already exists — skipping creation."
else
  echo "Creating Static Web App '$APP_NAME'..."
  az staticwebapp create \
    --name "$APP_NAME" \
    --resource-group "$RESOURCE_GROUP" \
    --location "$LOCATION" \
    --sku "$SKU" \
    --output none
fi

# ── deploy token ─────────────────────────────────────────────────────────────
echo ""
echo "Fetching deployment token..."
TOKEN=$(az staticwebapp secrets list \
  --name "$APP_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --query "properties.apiKey" -o tsv)

DEFAULT_HOSTNAME=$(az staticwebapp show \
  --name "$APP_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --query "defaultHostname" -o tsv)

echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Done!"
echo ""
echo " URL      : https://$DEFAULT_HOSTNAME"
echo ""
echo " GitHub Actions secret:"
echo "   Name  : AZURE_STATIC_WEB_APPS_API_TOKEN"
echo "   Value : $TOKEN"
echo ""
echo " Add it at:"
echo "   https://github.com/<org>/<repo>/settings/secrets/actions/new"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"