DEV Community

Cover image for Solved: Creating a Status Page using GitHub Issues and Netlify
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Creating a Status Page using GitHub Issues and Netlify

🚀 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.sh Bash script, executed during Netlify deployments, fetches open GitHub issues using gh api and jq, then dynamically injects formatted incident data into a static index.html template.
  • 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.

  1. 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.
  2. 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.
  3. 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).

  1. Navigate to GitHub Developer Settings:Go to your GitHub profile settings > Developer settings > Personal access tokens > Tokens (classic).
  2. 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_repo scope, or simply repo if you prefer (which includes public_repo). The key is to have read access to repository issues.
  3. 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.

  1. 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)
  2. index.html (Template):This is a basic HTML template. Our build.sh script 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>
Enter fullscreen mode Exit fullscreen mode

(Note: The <style> block is embedded for tutorial simplicity. In a real project, you’d link an external CSS file.)

  1. build.sh (Bash Script):This script fetches open issues, determines the overall status, and then injects this information into index.html. Replace YOUR_GITHUB_USER and YOUR_REPO_NAME with 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"
Enter fullscreen mode Exit fullscreen mode

Logic Explanation for build.sh:

  • It sets up the target output directory (public).
  • It copies the base index.html to public/index.html.
  • gh api is 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 jq command 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_STATUS based 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 -e to perform an in-place replacement, injecting the generated status HTML into the placeholder in public/index.html.
    1. 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.
Enter fullscreen mode Exit fullscreen mode

Logic Explanation for netlify.toml:

  • publish = "public": Tells Netlify that the static files to serve will be in the public directory 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.

  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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).
  2. Configure Build Settings:Netlify should automatically detect your netlify.toml. Confirm the “Build command” is chmod +x ./build.sh && ./build.sh and the “Publish directory” is public.
  3. 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 the gh CLI uses by default).
    • Value: Paste your GitHub Personal Access Token generated in Step 2.

Click “Save”.

  1. 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 generated public/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.

  1. 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 main or master). Copy the generated URL.
  2. 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”.

  1. Test the Automation:Go to your GitHub repository and open a new issue, or update an existing one (e.g., change a label from investigating to resolved). 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 least public_repo or repo scope 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.sh script (e.g., incorrect GitHub repository name, syntax errors in Bash or jq commands) 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 your build.sh script. 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.sh script to fetch closed issues with the resolved label 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-it if 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.


Darian Vance

👉 Read the original article on TechResolve.blog

Top comments (1)

Collapse
 
vortex_shadow_8bce7f9fe36 profile image
Hisham Idris

are this dev.to are clear of people