đ Executive Summary
TL;DR: This guide provides a cost-effective solution for creating a dynamic status page by leveraging GitHub Issues for incident management and Netlify for static site hosting. It streamlines communication by using incident data directly from GitHub Issues to automatically update the status page, minimizing manual overhead and costs.
đŻ Key Takeaways
- GitHub Issues serve as the content source for the status page, with labels (e.g., âinvestigatingâ, âmajor outageâ) defining incident status and severity.
- A
build.shBash script, executed during Netlify deployments, fetches open GitHub issues usinggh apiandjq, then dynamically injects formatted incident data into a staticindex.htmltemplate. - Automation is achieved by configuring Netlify build hooks and GitHub webhooks, ensuring the status page automatically updates whenever an issue is created or modified in the designated GitHub repository.
Creating a Status Page using GitHub Issues and Netlify
Introduction
In todayâs fast-paced digital landscape, maintaining transparency and keeping users informed about system status is paramount. Whether youâre running a small web service or a complex microservices architecture, unexpected outages or performance degradations are inevitable. The challenge often lies in communicating these incidents effectively without adding significant overhead or incurring hefty costs for dedicated status page services.
Manual updates are tedious and error-prone, consuming valuable time during critical incidents. Meanwhile, many enterprise-grade status page SaaS solutions, while feature-rich, can be prohibitively expensive for startups, side projects, or even established teams looking for a cost-effective alternative.
What if we told you thereâs a robust, highly customizable, and virtually free way to build a status page? This tutorial will guide you through creating a dynamic status page by leveraging two powerful and widely-used tools: GitHub Issues for incident management and Netlify for seamless static site hosting. This approach allows your incident management to double as your status page content source, streamlining your communication workflow and keeping costs to a minimum.
Prerequisites
Before we dive into the implementation, ensure you have the following set up:
- A GitHub Account: This is where your issues will live.
- A Netlify Account: For hosting your static status page.
- Basic familiarity with HTML and CSS: To customize the look and feel of your status page.
- Basic understanding of the command line interface (CLI).
- The GitHub CLI (
gh) installed locally: While not strictly required for Netlify builds, itâs invaluable for testing your API calls and managing issues locally.
Step-by-Step Guide
Step 1: Set Up Your GitHub Repository and Issues
First, we need a dedicated GitHub repository to house your status page issues. These issues will represent incidents or operational updates.
-
Create a New GitHub Repository:Go to GitHub and create a new public repository (e.g.,
my-service-status). Make sure itâs public so our static site can fetch issues without complex authentication for public information. -
Define Labels for Status:Labels are crucial for categorizing the status of your services. Navigate to your new repositoryâs âIssuesâ tab, then âLabelsâ. Create the following labels (or similar, adjust as needed):
-
operational(green color): For general system health (often not directly applied to incidents but can be a default state). -
investigating(orange color): When an incident is detected and being looked into. -
degraded performance(yellow color): Services are experiencing issues but are still partially functional. -
major outage(red color): Services are completely unavailable. -
resolved(blue color): The incident has been mitigated and services are restored.
-
-
Create a Sample Issue:Create an issue to test. Give it a descriptive title (e.g., âAPI Latency Spike in EU Regionâ). Apply a label like
investigating. Add comments for updates as the incident progresses.
Step 2: Generate a GitHub Personal Access Token (PAT)
To programmatically fetch issues from your repository during the Netlify build process, we need a GitHub Personal Access Token (PAT).
- Navigate to GitHub Developer Settings:Go to your GitHub profile settings > Developer settings > Personal access tokens > Tokens (classic).
-
Generate a New Token:Click âGenerate new token (classic)â.
- Note: While finer-grained tokens are available, for simplicity and compatibility with older APIs, classic tokens are often used for build scripts.
- Give your token a descriptive name (e.g., âNetlify Status Page Builderâ).
- Set an appropriate expiration (e.g., 90 days or custom).
- For public repositories, you typically only need
public_reposcope, or simplyrepoif you prefer (which includespublic_repo). The key is to have read access to repository issues.
- Copy the Token:IMPORTANT: Copy the generated token immediately. You wonât be able to see it again.
Step 3: Create Your Static Status Page and Build Script
Now, letâs create the core of our status page. Weâll use a simple HTML file and a Bash script that runs during the Netlify build to fetch and display the latest status from your GitHub issues.
-
Project Structure:Create a new directory for your project (e.g.,
status-page). Inside it, create the following files:-
index.html(your main status page template) -
build.sh(the script to fetch issues and generate content) -
netlify.toml(Netlify configuration)
-
-
index.html(Template):This is a basic HTML template. Ourbuild.shscript will inject content into the<div id="status-container"></div>element.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Service Status</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f7f6; color: #333; }
.container { max-width: 800px; margin: auto; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1, h2 { color: #0056b3; }
.status-header { display: flex; align-items: center; margin-bottom: 20px; }
.status-indicator { width: 20px; height: 20px; border-radius: 50%; margin-right: 10px; }
.operational { background-color: #28a745; } /* Green */
.degraded-performance { background-color: #ffc107; } /* Yellow */
.major-outage { background-color: #dc3545; } /* Red */
.investigating { background-color: #fd7e14; } /* Orange */
.resolved { background-color: #007bff; } /* Blue */
.issue { background-color: #f8f9fa; border: 1px solid #e9ecef; padding: 15px; margin-bottom: 15px; border-radius: 5px; }
.issue-title { font-weight: bold; margin-bottom: 5px; }
.issue-date { font-size: 0.8em; color: #6c757d; margin-bottom: 10px; }
.issue-body { font-size: 0.9em; line-height: 1.5; }
.no-incidents { color: #28a745; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>Service Status for TechResolve</h1>
<div id="status-container">
<!-- Content will be injected here by build.sh -->
</div>
</div>
</body>
</html>
(Note: The <style> block is embedded for tutorial simplicity. In a real project, youâd link an external CSS file.)
-
build.sh(Bash Script):This script fetches open issues, determines the overall status, and then injects this information intoindex.html. ReplaceYOUR_GITHUB_USERandYOUR_REPO_NAMEwith your actual GitHub username and repository name.
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# --- Configuration ---
GITHUB_REPO="YOUR_GITHUB_USER/YOUR_REPO_NAME" # e.g., techresolve/my-service-status
OUTPUT_DIR="public"
STATUS_TEMPLATE="index.html"
# --- End Configuration ---
# Ensure output directory exists
mkdir -p "$OUTPUT_DIR"
# Copy the template HTML to the output directory
cp "$STATUS_TEMPLATE" "$OUTPUT_DIR/index.html"
# Fetch open issues from GitHub
# We're specifically looking for issues with labels that signify an ongoing incident.
# The `gh` CLI uses the GITHUB_TOKEN environment variable for authentication.
ISSUES_JSON=$(gh api \
-H "Accept: application/vnd.github.v3+json" \
"/repos/$GITHUB_REPO/issues?state=open&labels=investigating,degraded%20performance,major%20outage" \
--jq '[.[] | {number: .number, title: .title, body: .body, created_at: .created_at, labels: [.labels[].name]}]')
# Determine overall status
OVERALL_STATUS="operational"
STATUS_TEXT="All Systems Operational"
STATUS_CLASS="operational"
if [ "$(echo "$ISSUES_JSON" | jq 'length')" -gt 0 ]; then
# Check for highest severity issue
if echo "$ISSUES_JSON" | jq '.[].labels[]' | grep -q "major outage"; then
OVERALL_STATUS="major outage"
STATUS_TEXT="Major Outage"
STATUS_CLASS="major-outage"
elif echo "$ISSUES_JSON" | jq '.[].labels[]' | grep -q "degraded performance"; then
OVERALL_STATUS="degraded performance"
STATUS_TEXT="Degraded Performance"
STATUS_CLASS="degraded-performance"
elif echo "$ISSUES_JSON" | jq '.[].labels[]' | grep -q "investigating"; then
OVERALL_STATUS="investigating"
STATUS_TEXT="Incident Investigating"
STATUS_CLASS="investigating"
fi
fi
# Build the status HTML content
STATUS_CONTENT="
<div class='status-header'>
<div class='status-indicator $STATUS_CLASS'></div>
<h2>$STATUS_TEXT</h2>
</div>
"
if [ "$(echo "$ISSUES_JSON" | jq 'length')" -eq 0 ]; then
STATUS_CONTENT+="<p class='no-incidents'>No active incidents reported.</p>"
else
# Loop through issues and format them
echo "$ISSUES_JSON" | jq -c '.[]' | while read -r ISSUE; do
ISSUE_TITLE=$(echo "$ISSUE" | jq -r '.title')
ISSUE_BODY=$(echo "$ISSUE" | jq -r '.body')
ISSUE_DATE=$(echo "$ISSUE" | jq -r '.created_at | split("T")[0]') # Just date part
# Simple HTML for the issue
STATUS_CONTENT+="
<div class='issue'>
<div class='issue-title'>Issue #$(echo "$ISSUE" | jq -r '.number'): $ISSUE_TITLE</div>
<div class='issue-date'>Reported: $ISSUE_DATE</div>
<div class='issue-body'>$(echo "$ISSUE_BODY" | sed 's/\\n/<br>/g')</div>
</div>
"
done
fi
# Inject the generated status content into the template
# We use Perl for in-place editing to handle potential special characters in content
perl -pi -e "s|<!-- Content will be injected here by build.sh -->|$STATUS_CONTENT|" "$OUTPUT_DIR/index.html"
echo "Status page generated successfully in $OUTPUT_DIR/index.html"
Logic Explanation for build.sh:
- It sets up the target output directory (
public). - It copies the base
index.htmltopublic/index.html. -
gh apiis used to fetch open issues from your specified GitHub repository. We filter by specific labels (investigating,degraded performance,major outage) to only show active incidents. - The
jqcommand is a powerful JSON processor used to parse the API response and extract relevant fields like issue number, title, body, creation date, and labels. - The script then determines the
OVERALL_STATUSbased on the severity of the open issues (e.g., if thereâs a âmajor outageâ issue, that becomes the primary status). - It constructs HTML snippets for the overall status indicator and for each individual active incident.
- Finally, it uses
perl -pi -eto perform an in-place replacement, injecting the generated status HTML into the placeholder inpublic/index.html.-
netlify.toml(Netlify Configuration):This file tells Netlify how to build and deploy your site.
-
[build]
publish = "public"
command = "chmod +x ./build.sh && ./build.sh"
[build.environment]
# IMPORTANT: GITHUB_TOKEN will be set in Netlify UI for security
# This section is just for local testing if you want to run `build.sh`
# GITHUB_TOKEN = "YOUR_PERSONAL_ACCESS_TOKEN_HERE"
# Remember to remove or comment this out before pushing to Git if it contains sensitive data.
Logic Explanation for netlify.toml:
-
publish = "public": Tells Netlify that the static files to serve will be in thepublicdirectory after the build. -
command = "chmod +x ./build.sh && ./build.sh": This is the build command Netlify will execute. It first makes our script executable, then runs it. -
[build.environment]: This section is where you could define environment variables for local testing. However, for the GitHub PAT, weâll configure it securely in Netlifyâs UI.
Step 4: Deploy to Netlify
With your files ready, letâs deploy your status page.
-
Commit and Push to GitHub:Initialize a Git repository in your project folder, commit all your files (
index.html,build.sh,netlify.toml), and push them to your GitHub repository (the same one you created in Step 1).
git init
git add .
git commit -m "Initial status page setup"
git branch -M main
git remote add origin https://github.com/YOUR_GITHUB_USER/YOUR_REPO_NAME.git
git push -u origin main
-
Connect to Netlify:Log in to Netlify. Click âAdd new siteâ > âImport an existing projectâ. Connect your GitHub account and select the repository you just pushed (e.g.,
my-service-status). -
Configure Build Settings:Netlify should automatically detect your
netlify.toml. Confirm the âBuild commandâ ischmod +x ./build.sh && ./build.shand the âPublish directoryâ ispublic. -
Set Environment Variable:This is crucial for security. Go to âSite settingsâ > âBuild & deployâ > âEnvironmentâ. Click âAdd a variableâ.
-
Key:
GITHUB_TOKEN(This is what theghCLI uses by default). - Value: Paste your GitHub Personal Access Token generated in Step 2.
-
Key:
Click âSaveâ.
-
Deploy Your Site:Trigger a deploy from the Netlify UI (or it might start automatically after saving the environment variable). Netlify will clone your repo, run
build.sh, and then serve the generatedpublic/index.html.
Step 5: Automate Updates with Netlify Build Hooks
To ensure your status page is always up-to-date without manual intervention, weâll set up a Netlify build hook that GitHub can trigger.
-
Create a Build Hook in Netlify:In your Netlify site settings, go to âBuild & deployâ > âBuild hooksâ. Click âAdd build hookâ. Give it a name (e.g., âGitHub Issues Updateâ) and select the branch you want to deploy from (usually
mainormaster). Copy the generated URL. -
Configure a GitHub Webhook:Go to your GitHub status repository > âSettingsâ > âWebhooksâ. Click âAdd webhookâ.
- Payload URL: Paste the Netlify build hook URL.
-
Content type: Select
application/json. -
Which events would you like to trigger this webhook? Select âLet me select individual eventsâ. Check
Issues. - Active: Ensure this is checked.
Click âAdd webhookâ.
-
Test the Automation:Go to your GitHub repository and open a new issue, or update an existing one (e.g., change a label from
investigatingtoresolved). After a few moments, you should see a new build triggered in Netlify, and your status page will update automatically!
Common Pitfalls
-
Incorrect GitHub PAT Permissions: If your Netlify build fails with a GitHub API error (e.g., 401 Unauthorized or 403 Forbidden), double-check that your Personal Access Token (
GITHUB_TOKEN) has the necessary read permissions (at leastpublic_repoorreposcope for classic tokens) and is correctly set in Netlifyâs environment variables. - GitHub API Rate Limits: For very high-traffic sites or complex issue fetching, you might hit GitHubâs API rate limits (5,000 requests per hour for authenticated users). For a typical status page, this is rarely an issue, but be mindful if you expand the script significantly. Unauthenticated requests have a much lower limit (60 requests per hour), which is why a PAT is crucial.
-
Build Script Errors: If your status page is blank or doesnât update, check the Netlify deploy logs. Errors in the
build.shscript (e.g., incorrect GitHub repository name, syntax errors in Bash orjqcommands) will halt the build process. -
Issue Label Mismatch: Ensure the labels you use in your GitHub issues (e.g.,
major outage) exactly match those being filtered and checked in yourbuild.shscript. Typos are common.
Conclusion
Youâve now successfully built a functional and automated status page leveraging the power of GitHub Issues and Netlify! This solution provides a cost-effective, transparent, and efficient way to communicate system status to your users. By integrating incident management directly with your communication platform, you minimize manual overhead during stressful situations and maintain a single source of truth.
From here, you can further enhance your status page:
- Custom Domain: Connect a custom domain to your Netlify site for a more professional look.
- Advanced Styling: Refine the CSS to match your brandâs aesthetics.
-
Historical Incidents: Modify the
build.shscript to fetch closed issues with theresolvedlabel and display them in a âpast incidentsâ section. - Service-Specific Status: Extend the system to show status for multiple services (e.g., using different issue labels or even separate repositories for each service).
-
Markdown Rendering: Use a Markdown parser (like
markdown-itif you convert to a Node.js build script) to render the issue body more nicely.
Embrace the power of simple, yet effective, DevOps tooling to keep your users informed and your operations smooth.

Top comments (1)
are this dev.to are clear of people