This guide covers the deployment process for the Scrum Guide Expansion Pack using Azure Static Web Apps and GitHub Actions.
- Production: scrumexpansion.org - Live production site
- Preview: agreeable-island-0c966e810-preview.centralus.6.azurestaticapps.net - Test environment for pre-production changes
The project uses Azure Static Web Apps for hosting with automated deployments triggered by GitHub Actions. The deployment pipeline follows a structured workflow through multiple environments.
Fork/Branch → PR (Test Site) → Merge to Main (Preview) → GitHub Release (Production)
Step-by-Step Process:
- Development: Create feature branch or fork and make changes
- Pull Request: PR creates automatic deployment to test site with unique URL
- Preview: Merge to
mainbranch automatically deploys to Preview environment - Production: Create GitHub Release with version tag to deploy to Production
- URL: scrumexpansion.org - Live production site
- Trigger: GitHub Release with version tag (e.g.,
v1.2.0) - Configuration:
staticwebapp.config.production.json - Hugo Config:
hugo.yaml - Deployment Method: Semantic versioning through GitHub Releases
- URL: agreeable-island-0c966e810-preview.centralus.6.azurestaticapps.net - Test environment for pre-production changes
- Trigger: Automatic deployment when PR is merged to
mainbranch - Configuration:
staticwebapp.config.preview.json - Hugo Config:
hugo.preview.yaml - Purpose: Pre-production validation before creating production release
- URL: https://agreeable-island-0c966e810-{PullRequestId}.centralus.6.azurestaticapps.net
- Trigger: Automatic deployment when Pull Request is created
- Configuration:
staticwebapp.config.canary.json - Hugo Config:
hugo.canary.yaml - Purpose: Test changes in isolation before merging
- Note: Only PRs to main repository get deployed (not from forks)
- Production: Uses custom domain
https://scrumexpansion.org - Preview: Azure Static Web Apps URL
https://agreeable-island-0c966e810-preview.centralus.6.azurestaticapps.net - Pull Request Environments: Dynamic URLs
https://agreeable-island-0c966e810-{PullRequestId}.centralus.6.azurestaticapps.net
Note: The baseURL in Hugo configuration files is typically set dynamically during the build process. Azure Static Web Apps automatically provides the correct URLs for each environment.
- agreeable-island-0c966e810: Azure-generated unique identifier for this Static Web App
- preview: Dedicated preview environment for testing pre-production changes
- {PullRequestId}: Replaced with actual PR number (e.g.,
agreeable-island-0c966e810-42.centralus.6.azurestaticapps.netfor PR #42) - centralus.6.azurestaticapps.net: Azure region and domain suffix
Each environment has its own configuration file:
| Environment | Config File | Hugo Config | Trigger |
|---|---|---|---|
| PR/Test | staticwebapp.config.canary.json |
hugo.canary.yaml |
Pull Request created |
| Preview | staticwebapp.config.preview.json |
hugo.preview.yaml |
Merge to main |
| Production | staticwebapp.config.production.json |
hugo.yaml |
GitHub Release created |
{
"routes": [
{
"route": "/",
"serve": "/index.html",
"statusCode": 200
},
{
"route": "/en/*",
"serve": "/en/index.html",
"statusCode": 200
},
{
"route": "/de/*",
"serve": "/de/index.html",
"statusCode": 200
},
{
"route": "/es/*",
"serve": "/es/index.html",
"statusCode": 200
},
{
"route": "/fr/*",
"serve": "/fr/index.html",
"statusCode": 200
}
],
"responseOverrides": {
"404": {
"serve": "/404.html",
"statusCode": 404
}
},
"mimeTypes": {
".pdf": "application/pdf"
},
"globalHeaders": {
"Cache-Control": "public, max-age=31536000, immutable"
},
"navigationFallback": {
"rewrite": "/index.html"
}
}- Multi-language routing for i18n support
- Custom 404 handling
- PDF mime type configuration
- Caching headers for performance
- SPA fallback routing
The deployment is triggered automatically when:
- Pull Request Created → Deploys to PR-specific test site (e.g.,
agreeable-island-0c966e810-42.centralus.6.azurestaticapps.net) - PR Merged to Main → Deploys to Preview environment (
agreeable-island-0c966e810-preview.centralus.6.azurestaticapps.net) - GitHub Release Created → Deploys to Production with version tag (
scrumexpansion.org)
Production deployments use semantic versioning through GitHub Releases:
| Version Type | Format | Use Case | Example |
|---|---|---|---|
| Patch | v1.0.1 |
Typo fixes, small corrections, tiny changes | v1.0.1 → Fix typo in guide |
| Minor | v1.1.0 |
New section, content additions, feature additions | v1.1.0 → Add new guide section |
| Major | v2.0.0 |
Complete document revamp, breaking changes, restructure | v2.0.0 → Complete guide overhaul |
Steps to deploy to production:
- Ensure all changes are merged to
mainbranch - Verify Preview site is working correctly
- Navigate to GitHub → Releases → Draft a new release
- Create or select a version tag following semantic versioning (e.g.,
v1.2.0) - Set target branch to
main - Add release title and description with changelog
- Click Publish release
- GitHub Actions automatically deploys to production
Example Release Notes:
## v1.2.0 - New Psychological Safety Guide
### Added
- New guide: Psychological Safety in Scrum Teams
- Translation support for German (DE)
### Changed
- Updated homepage layout
- Improved mobile navigation
### Fixed
- Corrected references in Complexity guide- Automatic PR Environments: Each pull request gets a unique URL
- Preview Environment: Dedicated preview environment for testing
- Production Deployment: Custom domain deployment
- Automatic SSL: Managed SSL certificates
- Global CDN: Worldwide content distribution
The repository uses several GitHub Actions workflows:
| Workflow | File | Purpose | Trigger |
|---|---|---|---|
| Build & Release | main.yaml |
Builds and deploys to all environments (PR test sites, Preview, Production) | Push to main, PR creation, Release creation |
| PR Cleanup | close-pr.yaml |
Cleans up PR-specific test environments | PR closed |
| Docs to Wiki | docs-to-wiki.yml |
Syncs documentation from /docs to GitHub Wiki |
Push to main (docs changes) |
| Copilot Setup | copilot-setup-steps.yml |
Development environment setup helper | Manual dispatch only |
| Tag External Edits | discussion-tag-external-edits.yml |
Tags discussions from external contributors | Discussion created/edited |
| Stale Issues | stale-issues.yml |
Automatically closes stale duplicate issues | Daily at 2:00 UTC |
| LEGACY | azure-static-web-apps-*.yml |
⛔ DO NOT USE - Disabled legacy workflow | Disabled |
Primary Deployment Workflow (main.yaml):
- Builds Hugo site with environment-specific configuration
- Deploys to Azure Static Web Apps
- Supports all three environments: PR test sites, Preview, and Production
- Handles version tagging and release management
Environment Cleanup (close-pr.yaml):
- Automatically removes PR-specific test environments from Azure
- Runs when a pull request is closed
- Prevents accumulation of unused test environments
Documentation Sync (docs-to-wiki.yml):
- Keeps GitHub Wiki synchronized with
/docsfolder - Runs automatically on documentation changes
- Can be manually triggered for full sync
baseURL: "https://scrumexpansion.org"
languageCode: "en-us"
title: "Scrum Guide Expansion Pack"
theme: ""
# Production-specific settings
minify: true
enableGitInfo: true
enableRobotsTXT: true
# Google Analytics
googleAnalytics: "G-XXXXXXXXXX"
# Security headers
security:
enableInlineShortcodes: false
exec:
allow: ["^dart-sass-embedded$", "^go$", "^npx$", "^postcss$"]baseURL: "https://agreeable-island-0c966e810-preview.centralus.6.azurestaticapps.net"
languageCode: "en-us"
title: "Scrum Guide Expansion Pack - Preview"
# Preview-specific settings
buildDrafts: true
buildFuture: true
enableRobotsTXT: false
# Disable analytics in preview
googleAnalytics: ""baseURL: "https://agreeable-island-0c966e810-{PullRequestId}.centralus.6.azurestaticapps.net"
languageCode: "en-us"
title: "Scrum Guide Expansion Pack - Canary"
# Canary-specific settings
buildDrafts: true
buildFuture: true
enableRobotsTXT: false
# Different analytics for canary
googleAnalytics: "G-CANARY-ID"Content and translations can be merged to main (and therefore visible on Preview) before they are ready for production. Two mechanisms prevent unfinished work from reaching scrumexpansion.org:
Languages that have not yet met the review requirements defined in translations-code-of-conduct.md (in particular, the minimum of five mother-tongue reviewers — see Section 4, items 5–6) should still be merged to main so they are visible on Preview for review. To prevent them from appearing on Production, set disabled: true for the language in site/hugo.production.yaml:
# site/hugo.production.yaml
languages:
tlh:
disabled: true # Klingon – reference language, never enabled
nl:
disabled: true # Dutch – awaiting reviewer sign-off
de:
disabled: true # German – awaiting reviewer sign-off
ro:
disabled: true # Romanian – awaiting reviewer sign-off
it:
disabled: true # Italian – awaiting reviewer sign-off
fa:
disabled: false # Farsi – review complete, enabled
es:
disabled: false # Spanish – review complete, enabledWorkflow:
- Translator opens PR with the new language content.
- PR is reviewed, merged to
main→ content is live on Preview for review. - The language entry in
hugo.production.yamlis set todisabled: true(or already is). - Reviewers validate the translation on Preview, and the translation guardian confirms the Translation Code of Conduct criteria are met.
- A follow-up PR sets
disabled: falsefor that language inhugo.production.yaml. - The next GitHub Release includes the newly enabled language in Production.
Individual documents, document versions, and document translations can be excluded from Production using Hugo's build settings with an environment target. This is useful for work-in-progress guides or translations that are not ready for public release.
Add the following to the front matter of the relevant content page:
# Prevents this page (and its children via cascade) from being
# listed or rendered in the production environment.
cascade:
- build:
list: never
render: never
target:
environment: productionWhere to apply it:
| Scope | File to edit | Effect |
|---|---|---|
| Entire document (all versions & translations) | site/content/{guide}/_index.md |
The cascade propagates to every child page under that guide |
| Single version (all languages for that version) | site/content/{guide}/{version}/index.md |
Only that version is hidden; other versions remain visible |
| Single translation | site/content/{guide}/{version}/index.{lang}.md |
Only that one translation is hidden |
Important: Hugo's
cascadedoes not propagate from the English page to other language files at the same level. If you need to gate a specific version for all languages, you must add thebuildblock to every language file in that version folder, or addcascadeto the parent_index.mdfor the document root.
Example — gate an entire guide (Planguage):
# site/content/planguage/_index.md
---
title: Planguage
cascade:
- build:
list: never
render: never
target:
environment: production
---This keeps the Planguage guide visible on Preview and Canary environments for review while hiding it from the production build at scrumexpansion.org.
Workflow:
- Author creates guide content and adds the
cascadebuild block to the guide's_index.md(or individual pages). - Content is merged to
main→ visible on Preview for review. - Once the guide is approved for public release, a follow-up PR removes the
cascadebuild block. - The next GitHub Release includes the guide on Production.
For a new guide written in a new language, both mechanisms may apply:
- The language itself is gated via
hugo.production.yaml(disabled: true). - The guide content is gated via
cascadebuild tags in its front matter.
Both must be resolved before the content appears on Production. They are independent — removing the build tag does not enable the language, and enabling the language does not remove the build tag.
- Azure CLI installed and configured
- Static Web Apps CLI installed
- Deployment tokens configured
# Build for production
cd site
hugo --environment production --minify
# Deploy using Azure CLI
cd ../public
swa deploy --env production# Trigger workflow manually
gh workflow run deploy.yml --ref main
# Check workflow status
gh run list --workflow=deploy.yml- Configure DNS to point to Azure Static Web Apps
- Add custom domain in Azure portal
- SSL certificate is automatically managed by Azure
# Production custom domain
CNAME scrumexpansion.org -> <static-web-app-url>
# Development environments use Azure Static Web Apps URLs:
# Preview: https://agreeable-island-0c966e810-preview.centralus.6.azurestaticapps.net
# PR environments: https://agreeable-island-0c966e810-{PullRequestId}.centralus.6.azurestaticapps.net
- Minification of HTML, CSS, and JS
- Image optimization with Hugo's image processing
- Asset bundling and compression
- Tree shaking for unused CSS
- CDN distribution through Azure
- Caching headers for static assets
- Gzip compression enabled
- HTTP/2 support
- Azure Application Insights for performance monitoring
- Google Analytics for user behavior tracking
- GitHub Actions logs for deployment monitoring
- Build times and success rates
- Page load performance
- User engagement metrics
- Error rates and 404s
Configure alerts for:
- Failed deployments
- High error rates
- Performance degradation
- SSL certificate expiration
# Check Hugo version locally
hugo version
# Test build locally
cd site
hugo --environment production --minify --verbose
# Check for template errors
hugo --templateMetrics- Check Azure Static Web Apps logs
- Verify GitHub Actions workflow
- Check configuration files
- Validate Hugo build output
- Verify DNS configuration
- Check Azure domain settings
- Wait for DNS propagation (up to 48 hours)
- Force SSL renewal if needed
- Check workflow logs in GitHub Actions
- Test build locally with same configuration
- Verify all secrets are configured correctly
- Check Azure resource status and logs
- Repository permissions managed through GitHub
- Azure resource access controlled via RBAC
- Deployment tokens secured as GitHub secrets
- Branch protection rules enforced
- Static files only - no server-side vulnerabilities
- HTTPS enforcement for all traffic
- Content Security Policy headers
- Regular dependency updates
# Add GitHub secret for Azure deployment
gh secret set AZURE_STATIC_WEB_APPS_API_TOKEN --body "your-token-here"
# List configured secrets
gh secret list- Create new patch release with reverted changes
- Create new release from previous commit
- Revert commit on main, let Preview validate, then create new release
- Re-publish previous GitHub Release (if supported)
# Revert to last known good commit
git revert HEAD --no-edit
git push origin main
# Verify in Preview environment first
# Then create new patch release (e.g., v1.2.1) to deploy to production
# Or reset to specific commit (use with caution)
git reset --hard <commit-hash>
git push --force-with-lease origin main
# Then create new releaseImportant: Always validate changes in the Preview environment before creating a production release.
- ✅ Test build locally with production settings
- ✅ Run content validation checks
- ✅ Review configuration changes
- ✅ Check for broken links
- ✅ Verify translations are complete
- ✅ Test on multiple devices/browsers
- ✅ Validate changes in Preview environment after merge to main
- ✅ Verify site loads correctly
- ✅ Check all languages work
- ✅ Test download links and PDFs
- ✅ Monitor performance metrics
- ✅ Check analytics tracking
- ✅ Validate SEO elements
When to deploy to each environment:
| Scenario | Action | Environment |
|---|---|---|
| Testing new feature | Create Pull Request | PR Test Site |
| Ready for team review | Merge to main | Preview |
| Ready for public release | Create GitHub Release | Production |
| Small typo fix | Create PR → Merge → Patch Release (v1.0.x) | All three |
| New content section | Create PR → Merge → Minor Release (v1.x.0) | All three |
| Major overhaul | Create PR → Merge → Major Release (vx.0.0) | All three |
- ✅ Verify site loads correctly
- ✅ Check all languages work
- ✅ Test download links and PDFs
- ✅ Monitor performance metrics
- ✅ Check analytics tracking
- ✅ Validate SEO elements
- Weekly: Review deployment logs and metrics
- Monthly: Update dependencies and Hugo version
- Quarterly: Security audit and performance review
- Annually: SSL certificate and domain renewal check
🔙 Back to: Documentation Home
➡️ Next: Content Management