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-cliCheck the installs:
git --version
gh --version
az versionSign in:
gh auth login
az login3. 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;
}
EOFIf 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 buildFor Vite, the final build folder is usually dist.
5. Test locally#
For a plain HTML site:
python3 -m http.server 5173Open:
http://localhost:5173For a Node-based project, use the project command:
npm run devStop the local server with Control-C.
6. Initialize git#
Create a .gitignore:
cat > .gitignore <<'EOF'
.DS_Store
node_modules/
dist/
.env
.env.local
EOFInitialize the repository:
git init
git add .
git commit -m "Initial static website"
git branch -M main7. Create the GitHub repository#
Create a new GitHub repo and push the code:
gh repo create "$PROJECT_NAME" --private --source=. --remote=origin --pushUse --public instead of --private if the site repo should be public.
Verify:
gh repo view --web
git status8. 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-githubFor a framework build, set the output location when Azure asks or update the generated GitHub Actions file later.
Common output locations:
| Project type | App location | Output 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 listWatch the deployment:
gh run watchIf the workflow file was created remotely by Azure, pull it locally:
git pull10. 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 tsvOpen:
https://PASTE-THE-HOSTNAME-HERE11. Make future updates#
Edit files locally, then commit and push:
git status
git add .
git commit -m "Update website"
git pushGitHub Actions should deploy the update automatically.
Watch it:
gh run watchDecision points#
| Decision | Option A | Option B | When to choose |
|---|---|---|---|
| Repository visibility | Private | Public | Use private for personal drafts or client work |
| Site type | Plain HTML | Framework | Use a framework when the site needs components, routing, or build steps |
| Deploy method | Azure Static Web Apps | Azure Storage static website | Use Static Web Apps when deploying from GitHub Actions |
| Region | westus2 | Another Azure region | Choose 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#
| Problem | Fix |
|---|---|
gh is not authenticated | Run gh auth login |
| Azure login fails | Run az login again and choose the correct subscription |
| Wrong Azure subscription | Run az account list --output table, then az account set --subscription "SUBSCRIPTION_NAME_OR_ID" |
| GitHub Actions cannot find the build output | Check app_location and output_location in .github/workflows/ |
| Plain HTML deploy tries to run a build | Leave output location blank and keep app location as / |
| Site URL shows an old version | Check the latest GitHub Actions run and wait for deployment to finish |
Local git push fails | Run 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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"