Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Yet Another Status Page

A modern, self-hosted status page built with Payload CMS and Next.js.

Features

  • Incident management — track and communicate service disruptions
  • Scheduled maintenance — plan and notify users about upcoming work
  • Email & SMS notifications — automatic subscriber alerts via SMTP and Twilio
  • Service groups — organize services into logical groups
  • Modern UI — responsive status page with dark mode
  • Self-hosted — full control over your data and infrastructure
  • Kubernetes-native — official Helm chart with bundled Postgres, Ingress, NetworkPolicy, and PDB
  • Container-friendly — multi-arch (amd64/arm64) image published to GHCR

Quick Start

The recommended way to deploy is the Helm chart on Kubernetes:

kubectl create namespace status

helm upgrade --install status \
  oci://ghcr.io/hostzero-gmbh/charts/yet-another-status-page \
  --namespace status \
  --set serverUrl=https://status.example.com \
  --set secret.payloadSecret=$(openssl rand -hex 32)

After the rollout, the status page is reachable at <serverUrl> and the admin panel at <serverUrl>/admin.

For evaluation on a single host, see Docker Compose. For local hacking, see Local Setup.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                 Yet Another Status Page                     │
├─────────────────────────────────────────────────────────────┤
│  Frontend (Next.js)          │  Admin Panel (Payload CMS)   │
│  - Status Page               │  - Manage Services           │
│  - Incident History          │  - Create Incidents          │
│  - Subscribe Form            │  - Schedule Maintenances     │
│                              │  - Send Notifications        │
├─────────────────────────────────────────────────────────────┤
│                     PostgreSQL Database                     │
└─────────────────────────────────────────────────────────────┘

Documentation

Installation

Yet Another Status Page can be deployed in several ways. The recommended path is the Helm chart on Kubernetes; everything else is provided for evaluation, single-host self-hosting, or managed PaaS users.

Prerequisites

PathRequires
Helm (recommended)Kubernetes >= 1.25, Helm >= 3.8, kubectl
Docker ComposeDocker + Docker Compose
From sourceNode.js 24+, PostgreSQL 15+
VercelA Vercel account

The official chart is published as an OCI artifact on GitHub Container Registry. Read the full Helm guide for configuration details.

kubectl create namespace status

helm upgrade --install status \
  oci://ghcr.io/hostzero-gmbh/charts/yet-another-status-page \
  --namespace status \
  --set serverUrl=https://status.example.com \
  --set secret.payloadSecret=$(openssl rand -hex 32)

Why Helm:

  • Built-in Postgres subchart (or bring-your-own via externalDatabase.existingSecret)
  • Ingress + TLS, NetworkPolicy, PodDisruptionBudget, persistent media uploads
  • Versioned chart releases that match the application image tag
  • Atomic upgrades and rollbacks (helm rollback)

Option 2: Vercel (One-Click)

Deploy instantly to Vercel with a managed PostgreSQL database:

Deploy with Vercel

This will:

  1. Create a new Vercel project
  2. Provision a Vercel Postgres database
  3. Prompt you to set PAYLOAD_SECRET (generate a random 32+ character string)

All configuration (site name, logos, services, notifications) is done through the admin panel — no code changes required.

Option 3: Docker Compose (single host)

Suitable for evaluation or small self-hosted setups. See the Docker Compose guide.

git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page

cp .env.example .env
# Edit .env: PAYLOAD_SECRET, POSTGRES_PASSWORD, SERVER_URL

docker compose up -d

Option 4: Pre-built Docker image (BYO Postgres)

docker run -d \
  --name status-page \
  -p 3000:3000 \
  -e DATABASE_URI=postgres://user:pass@host:5432/db \
  -e PAYLOAD_SECRET=$(openssl rand -hex 32) \
  -e SERVER_URL=https://status.example.com \
  ghcr.io/hostzero-gmbh/yet-another-status-page:latest

Option 5: Build from source

git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
npm install
npm run build
npm start

First-Time Setup

  1. Access the admin panel at <serverUrl>/admin.
  2. Create the first admin user when prompted.
  3. Configure the site under:
    • Configuration → Site Settings (name, description, favicon, logos)
    • Configuration → Email Settings (SMTP)
    • Configuration → SMS Settings (Twilio)
  4. Add services by creating service groups and services that represent your infrastructure.
  5. Go live at <serverUrl>.

Helm (Recommended)

The recommended way to deploy Yet Another Status Page in production is the official Helm chart, published as an OCI artifact to GitHub Container Registry. It bundles an optional PostgreSQL subchart, supports Ingress + TLS, persistent media uploads, NetworkPolicy, PodDisruptionBudget, and external databases.

Prerequisites

  • A Kubernetes cluster >= 1.25
  • Helm >= 3.8 (required for OCI registries)
  • kubectl configured for your cluster

Install / update Helm

# macOS (Homebrew)
brew install helm
brew upgrade helm

# Linux (script)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify
helm version

Quick install

# 1. Create a namespace
kubectl create namespace status

# 2. Generate a Payload secret (32+ chars)
PAYLOAD_SECRET=$(openssl rand -hex 32)

# 3. Install the chart
helm upgrade --install status \
  oci://ghcr.io/hostzero-gmbh/charts/yet-another-status-page \
  --namespace status \
  --set serverUrl=https://status.example.com \
  --set secret.payloadSecret="$PAYLOAD_SECRET"

The chart pulls the matching application image (ghcr.io/hostzero-gmbh/yet-another-status-page:v<chart appVersion>) and provisions a PostgreSQL StatefulSet via the bitnami subchart. After a minute or two, the deployment becomes ready and the admin panel is reachable at <serverUrl>/admin.

Updating

To upgrade to a newer chart and matching application version:

# Pin to a specific version (recommended for production)
helm upgrade status \
  oci://ghcr.io/hostzero-gmbh/charts/yet-another-status-page \
  --namespace status \
  --version 1.2.3 \
  --reuse-values

--reuse-values keeps your existing configuration. Drop it (and pass the same --set ... / -f values.yaml flags) if you want to change configuration during the upgrade.

To list available chart versions:

helm search repo oci://ghcr.io/hostzero-gmbh/charts/yet-another-status-page --versions

To roll back:

helm history status -n status
helm rollback status <revision> -n status

Configuration

All options are documented inline in the chart’s values.yaml. Common patterns:

Required

serverUrl: https://status.example.com
secret:
  payloadSecret: "<32+ random characters>"

Ingress with cert-manager

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: status.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: status-tls
      hosts:
        - status.example.com

External PostgreSQL

Disable the bundled subchart and point at your own database:

postgresql:
  enabled: false

externalDatabase:
  existingSecret: status-db
  existingSecretKey: DATABASE_URI

The referenced secret must contain a key with a full postgresql://user:password@host:5432/dbname URI.

Media uploads

Files uploaded through the Payload admin land in /app/public/media. By default the chart provisions a 5Gi ReadWriteOnce PVC. Increase or disable it via persistence.size / persistence.enabled. For multi-replica deployments, switch to object storage (e.g. configure @payloadcms/storage-vercel-blob via extraEnv) or use a ReadWriteMany storage class.

Hardening

networkPolicy:
  enabled: true        # restricts egress to DNS, Postgres, SMTP, HTTPS

podDisruptionBudget:
  enabled: true
  minAvailable: 1

replicaCount: 2        # requires RWX persistence or external storage

Backup and restore

When using the bundled PostgreSQL:

# Backup
kubectl exec -n status status-postgresql-0 -- \
  env PGPASSWORD=$(kubectl get secret -n status status-postgresql -o jsonpath='{.data.password}' | base64 -d) \
  pg_dump -U hostzero hostzero_status > backup.sql

# Restore
kubectl exec -i -n status status-postgresql-0 -- \
  env PGPASSWORD=$(kubectl get secret -n status status-postgresql -o jsonpath='{.data.password}' | base64 -d) \
  psql -U hostzero hostzero_status < backup.sql

For the media PVC, snapshot it via your CSI driver or copy out with kubectl cp.

Uninstall

helm uninstall status -n status

# PVCs are intentionally retained. Remove them explicitly if desired:
kubectl delete pvc -n status -l app.kubernetes.io/instance=status

Configuration

Yet Another Status Page is configured through environment variables and the admin panel.

Environment Variables

Required

VariableDescriptionExample
DATABASE_URIPostgreSQL connection stringpostgres://user:pass@host:5432/db
PAYLOAD_SECRETSecret key for encryption (min 32 chars) - Generate oneyour-super-secret-key-here-32ch
SERVER_URLPublic URL of your status pagehttps://status.example.com

Note: On Vercel, both POSTGRES_URL and SERVER_URL are automatically detected:

  • POSTGRES_URL is set when you add a Vercel Postgres database
  • SERVER_URL falls back to VERCEL_PROJECT_PRODUCTION_URL or VERCEL_URL if not explicitly set

The app supports both DATABASE_URI and POSTGRES_URL for database connections.

Vercel Deployment

When deploying to Vercel, you need to configure Vercel Blob storage for media uploads (since Vercel’s filesystem is read-only):

VariableDescriptionHow to Get
BLOB_READ_WRITE_TOKENVercel Blob storage tokenCreate a Blob store in your Vercel project

Steps to set up Vercel Blob:

  1. Go to your Vercel project dashboard
  2. Navigate to StorageCreate DatabaseBlob
  3. Copy the BLOB_READ_WRITE_TOKEN from the environment variables
  4. The token is automatically added to your deployment environment

Note: Media uploads will not work on Vercel without Vercel Blob storage configured.

Optional

VariableDescriptionDefault
PORTServer port3000
NODE_ENVEnvironment modeproduction

SSO/OIDC Authentication (Optional)

Enable Single Sign-On with any OIDC-compliant identity provider (Keycloak, Okta, Auth0, Azure AD, Google).

VariableDescriptionDefault
OIDC_CLIENT_IDOAuth2 client ID-
OIDC_CLIENT_SECRETOAuth2 client secret-
OIDC_AUTH_URLAuthorization endpoint-
OIDC_TOKEN_URLToken endpoint-
OIDC_USERINFO_URLUser info endpoint-
OIDC_SCOPESOAuth scopesopenid profile email
OIDC_AUTO_CREATECreate users on first logintrue
OIDC_ALLOWED_GROUPSComma-separated list of allowed groups(allow all)
OIDC_GROUP_CLAIMClaim name containing groupsgroups
OIDC_DISABLE_LOCAL_LOGINDisable password login (SSO-only)false

Provider-Specific URLs

Keycloak:

OIDC_AUTH_URL=https://keycloak.example.com/realms/{realm}/protocol/openid-connect/auth
OIDC_TOKEN_URL=https://keycloak.example.com/realms/{realm}/protocol/openid-connect/token
OIDC_USERINFO_URL=https://keycloak.example.com/realms/{realm}/protocol/openid-connect/userinfo

Okta:

OIDC_AUTH_URL=https://{domain}.okta.com/oauth2/default/v1/authorize
OIDC_TOKEN_URL=https://{domain}.okta.com/oauth2/default/v1/token
OIDC_USERINFO_URL=https://{domain}.okta.com/oauth2/default/v1/userinfo

Auth0:

OIDC_AUTH_URL=https://{tenant}.auth0.com/authorize
OIDC_TOKEN_URL=https://{tenant}.auth0.com/oauth/token
OIDC_USERINFO_URL=https://{tenant}.auth0.com/userinfo

Azure AD:

OIDC_AUTH_URL=https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
OIDC_TOKEN_URL=https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
OIDC_USERINFO_URL=https://graph.microsoft.com/oidc/userinfo

Google:

OIDC_AUTH_URL=https://accounts.google.com/o/oauth2/v2/auth
OIDC_TOKEN_URL=https://oauth2.googleapis.com/token
OIDC_USERINFO_URL=https://openidconnect.googleapis.com/v1/userinfo

Callback URL

When configuring your identity provider, set the callback/redirect URL to:

https://your-status-page.com/api/users/oauth/callback

Group-Based Access Control

To restrict access to specific groups from your identity provider:

  1. Configure your IdP to include group claims in the userinfo response
  2. Set OIDC_ALLOWED_GROUPS to a comma-separated list of allowed groups
  3. If your IdP uses a different claim name, set OIDC_GROUP_CLAIM

Example Keycloak Setup:

  1. Create a client scope named “groups” with a Group Membership mapper:
    • Token Claim Name: groups
    • Add to userinfo: On
  2. Add the scope to your client
  3. Configure the status page:
OIDC_SCOPES=openid profile email groups
OIDC_ALLOWED_GROUPS=status-page-admins,status-page-editors

SSO-Only Mode

To disable password login and require SSO for all users:

OIDC_DISABLE_LOCAL_LOGIN=true

Warning: Ensure SSO is working correctly before enabling this option, or you may lock yourself out!

Admin Panel Settings

The admin panel has three configuration sections under Configuration:

Site Settings

Access Configuration → Site Settings to configure:

  • Site Name: Displayed in the header and emails
  • Site Description: Meta description for SEO
  • Favicon: Custom favicon for your status page
  • Logos: Light and dark theme logos
  • SEO: Meta titles and descriptions
  • Status Override: Maintenance mode and custom messages

Email Settings

Access Configuration → Email Settings to configure email notifications:

SettingDescription
Enable Email SubscriptionsAllow users to subscribe via email
SMTP HostYour mail server hostname
SMTP PortUsually 587 (TLS) or 465 (SSL)
SMTP SecurityNone, TLS, or SSL
SMTP UsernameAuthentication username
SMTP PasswordAuthentication password
From AddressSender email address
From NameSender display name
Reply-ToReply-to address (optional)

SMS Settings

Access Configuration → SMS Settings to configure SMS notifications:

SettingDescription
Enable SMS SubscriptionsAllow users to subscribe via SMS
Account SIDYour Twilio Account SID
Auth TokenYour Twilio Auth Token
From NumberYour Twilio phone number (required if not using Messaging Service)
Messaging Service SIDAlternative to From Number for better deliverability

SMS Templates

You can customize the SMS message templates with these placeholders:

PlaceholderDescription
{{siteName}}Your site name from Site Settings
{{title}}Incident or maintenance title
{{status}}Current status (e.g., Investigating, Resolved)
{{message}}Update message content
{{schedule}}Maintenance schedule (maintenance only)
{{url}}Link to the incident/maintenance page

Available templates:

  • New Incident Template - For initial incident notifications
  • Incident Update Template - For incident status updates
  • New Maintenance Template - For scheduled maintenance announcements
  • Maintenance Update Template - For maintenance status updates

You can also configure Title Max Length and Message Max Length to control truncation.

Testing Notifications

After configuring SMTP or Twilio:

  1. Create a test subscriber in Notifications → Subscribers
  2. Create a test incident in Status → Incidents
  3. Check the Notifications collection for the auto-generated draft
  4. Click Send Notification Now to test

Security Recommendations

  1. Use strong secrets: Generate a random 32+ character string for PAYLOAD_SECRET
  2. Use HTTPS: Always deploy behind HTTPS in production
  3. Secure database: Use strong passwords and restrict database access
  4. Regular backups: Schedule regular database backups

Demo Mode - Complete Guide

A fully functional live demo system for YASP that allows users to try all features with automatic hourly database resets.

⚠️ CRITICAL WARNING: Demo mode is DESTRUCTIVE. It will permanently delete all application data at regular intervals. Only use on a dedicated/disposable database. NEVER enable on production or any database containing data you want to keep.

📋 Table of Contents


Quick Start

Get your live demo running in 5 minutes!

1. Create .env file

cp .env.example .env

Add these lines to .env:

⚠️ WARNING: Only enable demo mode on a disposable database!

DEMO_MODE=true
DEMO_USER_EMAIL=demo@yasp.io
DEMO_USER_PASSWORD=demo2026#
DEMO_RESET_INTERVAL_MINUTES=60
docker compose -f docker-compose.demo.yml up -d

Wait ~2 minutes for the first reset to complete, then access:

  • Status Page: http://localhost:3000
  • Admin Panel: http://localhost:3000/admin
    • Email: demo@yasp.io
    • Password: demo2026#

3. OR Start Locally

# Install dependencies
npm install

# Start the app with demo mode enabled
DEMO_MODE=true npm run dev

When DEMO_MODE=true, the app will seed the database and schedule automatic resets on startup. There is no separate scheduler process to run.

To trigger a manual reseed at any time (useful in development):

npm run demo:seed

Overview

⚠️ IMPORTANT: Demo mode will delete all data in the database periodically. Use only on dedicated demo infrastructure.

Demo Mode allows you to run a fully functional live demo of YASP where users can:

Try all features - Create incidents, services, maintenance, send notifications
Explore the admin panel - Full access to all CMS features
Test the workflow - Experience the complete status page lifecycle
Cannot change password - Demo user password is protected
🔄 Auto-reset - Database resets to demo state every hour (configurable)

What You Get

Demo Banner

  • Purple banner at the top showing:
    • Live demo indicator
    • Countdown to next reset
    • Password change disabled notice

Sample Data

  • 6 Services across 3 groups
  • 3 Incidents (resolved, ongoing, old)
  • 2 Scheduled Maintenances
  • 2 Subscribers

Full Features

  • ✅ Create/edit/delete incidents
  • ✅ Manage services and groups
  • ✅ Schedule maintenance
  • ✅ Send notifications (draft mode)
  • ✅ Manage subscribers
  • ❌ Cannot change demo user password
  • 🔄 Resets every hour

Setup

Environment Variables

Add these variables to your .env file:

# Enable demo mode
DEMO_MODE=true

# Demo user credentials (users will use these to login)
DEMO_USER_EMAIL=demo@yasp.io
DEMO_USER_PASSWORD=demo2026#

# Reset interval in minutes (default: 60)
DEMO_RESET_INTERVAL_MINUTES=60

Initial Seed

Seed the database with demo data:

npm run demo:seed

This creates:

  • Demo user account
  • 3 service groups (API Services, Infrastructure, Web Applications)
  • 6 services with various statuses
  • 3 sample incidents (resolved, ongoing, old)
  • 2 scheduled maintenances
  • 2 sample subscribers
  • Configured settings

Automatic Reset Scheduling

When DEMO_MODE=true, the app schedules automatic resets in-process on startup (via Payload’s onInit hook). It will:

  • Reseed the database immediately on first boot
  • Schedule recurring resets at DEMO_RESET_INTERVAL_MINUTES (default 60)
  • Log each reset operation to the app’s stdout
  • Run for the lifetime of the app container

No separate scheduler container or process is required.

Docker Deployment

For Docker-based demo deployments, use the shipped docker-compose.demo.yml (at the repo root), which sets DEMO_MODE=true and the related env vars on the single app container:

docker compose -f docker-compose.demo.yml up -d

Features

Demo Banner

When demo mode is enabled, a purple gradient banner appears at the top of the admin panel showing:

  • “Live Demo Mode” indicator
  • Real-time countdown timer until next reset
  • Reminder that password changes are disabled

The banner:

  • Updates every second
  • Responsive design
  • Smooth animations
  • Auto-hides when demo mode is disabled

Password Protection

The demo user’s password cannot be changed through the admin panel. Any attempts to change it are silently blocked using Payload CMS hooks.

Implementation:

// src/collections/Users.ts
hooks: {
  beforeChange: [
    ({ data, req, operation }) => {
      if (isDemoMode() && data?.email === getDemoUserEmail()) {
        if (data.password && operation === 'update') {
          delete data.password  // Block password changes
        }
      }
      return data
    },
  ],
}

Database Reset

The reset process:

  1. Clears all user-generated data (incidents, services, etc.)
  2. Preserves the demo user account
  3. Re-seeds with fresh demo data
  4. Logs the operation with timestamp

Reset Logs:

🔄 Starting scheduled demo reset...
⏰ Reset time: 2026-03-02T10:00:00.000Z
🗑️  Clearing existing data...
👤 Creating demo user...
⚙️  Updating settings...
📁 Creating service groups...
🔧 Creating services...
🚨 Creating sample incidents...
🔧 Creating scheduled maintenance...
📧 Creating sample subscribers...
✅ Demo reset completed successfully!
📅 Next reset: 2026-03-02T11:00:00.000Z

Demo Data

Service Groups

  • API Services - Core API endpoints
  • Infrastructure - Hosting and infrastructure
  • Web Applications - Frontend apps

Services

ServiceStatusGroup
REST API✅ OperationalAPI Services
GraphQL API✅ OperationalAPI Services
Authentication⚠️ DegradedAPI Services
Database✅ OperationalInfrastructure
CDN✅ OperationalInfrastructure
Web Dashboard✅ OperationalWeb Applications

Incidents

1. API Gateway Latency Issues (Resolved, 3 hours ago)

  • Status progression: Investigating → Identified → Monitoring → Resolved
  • Affected: REST API, GraphQL API
  • Timeline with 4 updates showing the resolution process

2. Authentication Service Degraded Performance (Ongoing, 30 minutes ago)

  • Status progression: Investigating → Identified
  • Affected: Authentication
  • Active incident showing current investigation

3. CDN Cache Invalidation Delay (Resolved, 2 days ago)

  • Status progression: Investigating → Resolved
  • Affected: CDN
  • Historical incident for reference

Scheduled Maintenance

1. Database Cluster Upgrade (7 days from now)

  • Duration: ~2 hours
  • Affected: Database, REST API, GraphQL API
  • Description: PostgreSQL upgrade for improved performance

2. CDN Configuration Update (3 days from now)

  • Duration: ~30 minutes
  • Affected: CDN, Web Dashboard
  • Description: CDN configuration improvements

Subscribers

  • Email subscriber: user1@example.com (verified)
  • SMS subscriber: +1234567890 (verified)

Configuration

NPM Scripts

npm run demo:seed        # Seed database with demo data
npm run demo:reset       # Manually reset database

When the app runs with DEMO_MODE=true, recurring resets are scheduled automatically inside the app process. The scripts above are only needed for manual one-off operations (e.g. local development).

Change Reset Interval

Modify DEMO_RESET_INTERVAL_MINUTES to change how often the database resets:

# Reset every 30 minutes
DEMO_RESET_INTERVAL_MINUTES=30

# Reset every 2 hours
DEMO_RESET_INTERVAL_MINUTES=120

# Reset every 24 hours
DEMO_RESET_INTERVAL_MINUTES=1440

Change Demo Credentials

DEMO_USER_EMAIL=admin@example.com
DEMO_USER_PASSWORD=SecureDemo123!

Customize Demo Data

Edit scripts/seed-demo-data.ts to customize:

  • Service names and statuses
  • Incident content and timelines
  • Maintenance schedules
  • Site settings and branding
  • Subscriber data

Customize Demo Banner

Edit src/components/admin/DemoBanner.tsx and DemoBanner.scss to change:

  • Banner appearance and colors
  • Message content
  • Timer display format
  • Animation styles

Manual Operations

Manual Reset

Reset the database manually at any time:

npm run demo:reset

Or via Docker:

docker compose exec app npm run demo:reset

Seed Only

Seed demo data without clearing existing data:

npm run demo:seed

Check Demo Status

curl http://localhost:3000/api/demo-status

Response:

{
  "isDemoMode": true,
  "timeUntilReset": "45m 23s",
  "resetIntervalMinutes": 60
}

Production Deployment

Docker

Use the provided docker-compose.demo.yml:

# Start
docker compose -f docker-compose.demo.yml up -d

# View logs (reset operations log to the app container)
docker compose -f docker-compose.demo.yml logs -f app

# Stop
docker compose -f docker-compose.demo.yml down

Kubernetes / Helm

Enable demo mode by setting DEMO_MODE=true (and the related env vars) on the main app deployment - no second deployment is needed. With the shipped Helm chart, this can be done via the demo.* values:

demo:
  enabled: true
  userEmail: demo@yasp.io
  userPassword: demo2026#
  resetIntervalMinutes: 60

Troubleshooting

Symptoms: Demo banner doesn’t appear in admin panel

Solutions:

  1. Verify DEMO_MODE=true in environment:

    echo $DEMO_MODE  # Should output: true
    
  2. Check API endpoint returns correct data:

    curl http://localhost:3000/api/demo-status
    
  3. Clear browser cache and hard refresh:

    • Mac: Cmd+Shift+R
    • Windows: Ctrl+Shift+R
  4. Restart the application

Auto-Reset Not Running

Symptoms: Database doesn’t reset automatically

Solutions:

  1. Check the app logs for the demo scheduler startup message:

    docker compose -f docker-compose.demo.yml logs app | grep -i demo
    

    You should see Demo mode scheduler initialized near startup.

  2. Verify DEMO_MODE=true is set on the running container:

    docker compose -f docker-compose.demo.yml exec app printenv DEMO_MODE
    
  3. Trigger a one-off reset to confirm the seed logic works:

    docker compose -f docker-compose.demo.yml exec app npm run demo:reset
    

Reset Not Working

Symptoms: Manual reset fails or doesn’t complete

Solutions:

  1. Manually trigger a reset:

    npm run demo:reset
    
  2. Check database connection:

    docker compose exec db psql -U hostzero -d hostzero_status -c "SELECT COUNT(*) FROM incidents;"
    
  3. Verify database permissions

  4. Check disk space

Password Still Changeable

Symptoms: Demo user can change password

Solutions:

  1. Verify demo mode is enabled:

    echo $DEMO_MODE
    
  2. Check user email matches DEMO_USER_EMAIL:

    echo $DEMO_USER_EMAIL
    
  3. Review server logs for hook execution:

    docker compose logs app | grep "Demo Mode"
    
  4. Restart the application

Can’t Login

Symptoms: Unable to login with demo credentials

Solutions:

  1. Verify credentials match .env file:

    echo $DEMO_USER_EMAIL
    echo $DEMO_USER_PASSWORD
    
  2. Check database is running:

    docker compose ps db
    
  3. Reset database:

    npm run demo:reset
    
  4. Check for user in database:

    docker compose exec db psql -U hostzero -d hostzero_status -c "SELECT email FROM users;"
    

Security

Public Demo Considerations

For public-facing demos:

Required:

  • ✅ Use a strong PAYLOAD_SECRET (32+ characters)
  • ✅ Don’t expose sensitive data in demo content
  • ✅ Monitor resource usage and costs
  • ✅ Set reasonable reset intervals (30-60 minutes)

Monitoring

Logs

The scheduler logs each reset operation:

🔄 Starting scheduled demo reset...
⏰ Reset time: 2026-03-02T10:00:00.000Z
🗑️  Clearing existing data...
👤 Creating demo user...
⚙️  Updating settings...
📁 Creating service groups...
🔧 Creating services...
🚨 Creating sample incidents...
🔧 Creating scheduled maintenance...
📧 Creating sample subscribers...
✅ Demo reset completed successfully!
📅 Next reset: 2026-03-02T11:00:00.000Z

View logs:

# Docker
docker compose -f docker-compose.demo.yml logs -f app

# Local
# Check the terminal where the app (`npm run dev` with DEMO_MODE=true)
# is running.

API Reference

Demo Status Endpoint

Endpoint: GET /api/demo-status

Description: Returns current demo mode status and reset information

Response:

{
  "isDemoMode": true,
  "timeUntilReset": "45m 23s",
  "resetIntervalMinutes": 60
}

Example:

curl http://localhost:3000/api/demo-status

Support

Documentation

Community

Need Help?

  1. Check this documentation
  2. Search existing GitHub issues
  3. Ask in GitHub Discussions
  4. Create a new issue with details

Admin Overview

The Yet Another Status Page admin panel is powered by Payload CMS and provides a comprehensive interface for managing your status page.

Dashboard

The dashboard shows at-a-glance metrics:

  • Active Incidents - Current unresolved incidents
  • Upcoming Maintenances - Scheduled or in-progress maintenance windows
  • Draft Notifications - Notifications waiting to be sent
  • Scheduled Notifications - Notifications being processed
  • Email Subscribers - Active email subscribers
  • SMS Subscribers - Active SMS subscribers

The admin panel is organized into sections:

Status

  • Service Groups - Logical groupings of services
  • Services - Individual services to monitor
  • Incidents - Service disruptions and issues
  • Maintenances - Scheduled maintenance windows

Notifications

  • Notifications - Manage and send notifications
  • Subscribers - Manage subscriber list

Admin

  • Users - Admin user accounts
  • Media - Uploaded files and images

Configuration

  • Site Settings - Site name, branding, SEO, status overrides
  • Email Settings - SMTP configuration and email subscriptions
  • SMS Settings - Twilio configuration, SMS subscriptions, and message templates

Workflow Overview

┌─────────────────────────────────────────────────────────────┐
│  1. Create Services                                         │
│     Define your infrastructure components                    │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  2. Incident Occurs                                         │
│     Create incident → Notification draft auto-created       │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  3. Review & Send                                           │
│     Go to Notifications → Review → Send                     │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  4. Add Updates                                             │
│     Post updates → New notification drafts auto-created     │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  5. Resolve                                                 │
│     Mark resolved → Final notification                      │
└─────────────────────────────────────────────────────────────┘

Quick Actions

Creating an Incident

  1. Go to Status → Incidents
  2. Click Create New
  3. Fill in the title, affected services, status, and impact
  4. Click Save
  5. A notification draft is automatically created

Scheduling Maintenance

  1. Go to Status → Maintenances
  2. Click Create New
  3. Set the title, affected services, and schedule
  4. Click Save
  5. A notification draft is automatically created

Sending Notifications

  1. Go to Notifications → Notifications
  2. Find the draft notification
  3. Review and edit the content if needed
  4. Click Send Notification Now

Managing Services

Services represent the components of your infrastructure that you want to display on the status page.

Service Groups

Service groups organize related services together.

Creating a Service Group

  1. Go to Status → Service Groups
  2. Click Create New
  3. Enter a name (e.g., “Core Infrastructure”, “API Services”)
  4. Optionally add a description
  5. Click Save

Ordering Groups

Drag and drop service groups to reorder them on the status page.

Services

Services are individual components within a group.

Creating a Service

  1. Go to Status → Services
  2. Click Create New
  3. Fill in:
    • Name - Display name (e.g., “API Gateway”)
    • Description - Brief description
    • Service Group - Which group it belongs to
    • Status - Current operational status
  4. Click Save

Service Statuses

StatusColorDescription
Operational🟢 GreenService is working normally
Degraded Performance🟡 YellowService is slow or partially impaired
Partial Outage🟠 OrangeSome functionality unavailable
Major Outage🔴 RedService is completely unavailable
Under Maintenance🔵 BlueService is undergoing planned maintenance

Automatic Status Updates

Service status is automatically updated when:

  • An incident is created affecting the service
  • An incident is resolved
  • A maintenance window starts or ends

You can also manually update the status at any time.

Best Practices

Naming

  • Use clear, user-facing names
  • Avoid internal jargon
  • Be consistent with naming conventions

Grouping

  • Group by function (e.g., “Core”, “APIs”, “Integrations”)
  • Keep groups manageable (5-10 services each)
  • Consider your users’ perspective

Granularity

  • Not too broad (users need to know what’s affected)
  • Not too narrow (too many services is overwhelming)
  • Aim for 10-30 total services for most deployments

Managing Incidents

Incidents represent unplanned service disruptions or issues affecting your infrastructure.

Creating an Incident

  1. Go to Status → Incidents
  2. Click Create New
  3. Fill in the incident details:
    • Title - Brief description (e.g., “API Gateway Latency Issues”)
    • Affected Services - Select impacted services
    • Status - Current investigation status
    • Impact - Severity level
  4. Click Save

A notification draft is automatically created when you save.

Incident Statuses

StatusDescription
InvestigatingIssue detected, investigating cause
IdentifiedRoot cause identified, working on fix
MonitoringFix applied, monitoring for stability
ResolvedIssue fully resolved

Status Flow

Investigating → Identified → Monitoring → Resolved

You can skip statuses if appropriate (e.g., go directly to Resolved for quick fixes).

Impact Levels

ImpactDescriptionDisplay
OperationalNo user impact (informational)🟢 Green
Degraded PerformanceSlower than normal🟡 Yellow
Partial OutageSome functionality unavailable🟠 Orange
Major OutageService completely unavailable🔴 Red

Adding Updates

As the incident progresses, add updates to the timeline:

  1. Open the incident
  2. Scroll to Updates
  3. Click Add Update
  4. Fill in:
    • Status - Current status
    • Message - Update details
    • Created At - When this update occurred
  5. Click Save

A new notification draft is automatically created for each update.

Resolving an Incident

  1. Open the incident
  2. Change Status to “Resolved”
  3. The Resolved At timestamp is automatically set
  4. Click Save
  5. Review and send the final notification

Each incident gets a unique short ID (e.g., abc123) that creates a permanent link:

https://status.example.com/i/abc123

This link is included in notifications and remains valid even if the title changes.

Best Practices

Titles

  • Be specific but concise
  • Include the affected component
  • Avoid blame or technical jargon

Good: “Payment Processing Delays” Bad: “Database server crashed due to OOM killer”

Updates

  • Post updates every 30-60 minutes during active incidents
  • Be honest about what you know and don’t know
  • Set expectations for next update

Resolution

  • Confirm the issue is fully resolved before closing
  • Include a brief summary of what happened
  • Thank users for their patience

Example Timeline

┌─────────────────────────────────────────────────────────────┐
│ API Gateway Latency Issues                                  │
├─────────────────────────────────────────────────────────────┤
│ 🟡 Investigating - 10:00 AM                                 │
│    We are investigating reports of slow API responses.      │
├─────────────────────────────────────────────────────────────┤
│ 🟡 Identified - 10:30 AM                                    │
│    Root cause identified as a misconfigured load balancer.  │
│    Our team is implementing a fix.                          │
├─────────────────────────────────────────────────────────────┤
│ 🟢 Monitoring - 11:00 AM                                    │
│    Fix deployed. We are monitoring for stability.           │
├─────────────────────────────────────────────────────────────┤
│ 🟢 Resolved - 11:30 AM                                      │
│    This incident has been resolved. API response times      │
│    have returned to normal.                                 │
└─────────────────────────────────────────────────────────────┘

Managing Maintenances

Maintenances represent planned service interruptions or maintenance windows.

Creating a Maintenance

  1. Go to Status → Maintenances
  2. Click Create New
  3. Fill in the maintenance details:
    • Title - Brief description (e.g., “Database Migration”)
    • Description - Detailed explanation (optional)
    • Affected Services - Select impacted services
    • Scheduled Start - When maintenance begins
    • Scheduled End - When maintenance ends (optional)
    • Duration - Human-readable duration (e.g., “~2 hours”)
  4. Click Save

A notification draft is automatically created when you save.

Maintenance Statuses

StatusDescription
UpcomingScheduled but not yet started
In ProgressCurrently underway
CompletedSuccessfully finished
CancelledMaintenance was cancelled

Auto-Status Updates

Enable automatic status transitions:

  • Auto-start on schedule - Automatically changes to “In Progress” when the scheduled start time is reached
  • Auto-complete on schedule - Automatically changes to “Completed” when the scheduled end time is reached

These can be enabled/disabled per maintenance.

Adding Updates

During maintenance, add updates to keep users informed:

  1. Open the maintenance
  2. Scroll to Updates
  3. Click Add Update
  4. Fill in:
    • Status - Current status
    • Message - Progress update
    • Created At - When this update occurred
  5. Click Save

A new notification draft is automatically created for each update.

Each maintenance gets a unique short ID (e.g., xyz789) that creates a permanent link:

https://status.example.com/m/xyz789

Notification Content

Initial Notification (Email)

A maintenance window has been scheduled.

Scheduled Start: Sat, Jan 11 at 2:00 AM
Scheduled End: Sat, Jan 11 at 4:00 AM
Expected Duration: ~2 hours

We will notify you when the maintenance begins and completes.

View full details: https://status.example.com/m/xyz789

Initial Notification (SMS)

🔧 MAINTENANCE: Database Migration

📅 Sat, Jan 11 at 2:00 AM - Sat, Jan 11 at 4:00 AM

We will notify you when maintenance begins and completes.

Details: https://status.example.com/m/xyz789

Best Practices

Scheduling

  • Schedule during low-traffic periods
  • Give users at least 24-48 hours notice
  • Avoid scheduling during holidays or major events

Communication

  • Be clear about what will be affected
  • Provide estimated duration
  • Notify at key milestones (start, 50%, complete)

Timing

  • Send initial notification 24-48 hours before
  • Send reminder 1-2 hours before
  • Send “started” notification when beginning
  • Send “completed” notification when done

Example Timeline

┌─────────────────────────────────────────────────────────────┐
│ Database Migration                                          │
│ Scheduled: Jan 11, 2:00 AM - 4:00 AM EST                   │
├─────────────────────────────────────────────────────────────┤
│ 📅 Scheduled - Jan 9, 10:00 AM                              │
│    Scheduled maintenance for database migration.            │
├─────────────────────────────────────────────────────────────┤
│ 🔧 In Progress - Jan 11, 2:00 AM                           │
│    Maintenance has begun. Services may be unavailable.      │
├─────────────────────────────────────────────────────────────┤
│ 🔧 In Progress - Jan 11, 3:00 AM                           │
│    Migration 75% complete. On track for scheduled end.      │
├─────────────────────────────────────────────────────────────┤
│ ✅ Completed - Jan 11, 3:45 AM                              │
│    Maintenance completed successfully. All services         │
│    have been restored.                                      │
└─────────────────────────────────────────────────────────────┘

Notification Workflow

Yet Another Status Page includes a powerful notification system that automatically creates notification drafts and allows you to review before sending.

How It Works

┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│  Create/Update   │ ──▶ │  Draft Created   │ ──▶ │  Review & Send   │
│  Incident or     │     │  Automatically   │     │  from Admin      │
│  Maintenance     │     │                  │     │                  │
└──────────────────┘     └──────────────────┘     └──────────────────┘

Automatic Draft Creation

Notification drafts are automatically created when:

  1. New Incident Created - A draft with incident details is created
  2. Incident Updated - When you add an update to the timeline, a new draft is created
  3. New Maintenance Scheduled - A draft with schedule details is created
  4. Maintenance Updated - When you add an update, a new draft is created

Manual Review & Send

Notifications are never sent automatically. You must:

  1. Go to Notifications → Notifications
  2. Review the draft content
  3. Edit if needed
  4. Click Send Notification Now

This gives you full control over what gets sent to subscribers.

Notification Statuses

StatusDescription
DraftCreated but not sent. Can be edited.
ScheduledBeing processed for sending.
SentSuccessfully delivered to subscribers.
FailedSending failed. Can retry.

Notification Channels

Each notification can be sent via:

  • Email - Sends to email subscribers only
  • SMS - Sends to SMS subscribers only
  • Both - Sends to all subscribers

Email Notifications

Content

Email notifications include:

  • Subject line
  • Formatted HTML body
  • Call-to-action button linking to the status page
  • Unsubscribe link (required for compliance)

Headers

Emails automatically include:

  • List-Unsubscribe header for one-click unsubscribe
  • List-Unsubscribe-Post header for RFC 8058 compliance

Configuration

Configure SMTP in Configuration → Email Settings:

  • Enable Email Subscriptions toggle
  • SMTP Host, Port, Security
  • Authentication credentials
  • From address and name

SMS Notifications

Content

SMS messages are generated from customizable templates and include:

  • Site name prefix
  • Emoji indicator (🚨 incident, 🔧 maintenance)
  • Title and status
  • Scheduled times (for maintenance)
  • Link to status page

Configuration

Configure Twilio in Configuration → SMS Settings:

  • Enable SMS Subscriptions toggle
  • Account SID
  • Auth Token
  • From phone number OR Messaging Service SID

SMS Templates

You can customize SMS message templates in Configuration → SMS Settings under the “SMS Templates” section. Available placeholders:

  • {{siteName}} - Your site name
  • {{title}} - Incident/maintenance title
  • {{status}} - Current status
  • {{message}} - Update message
  • {{schedule}} - Maintenance schedule
  • {{url}} - Link to the page

Configure Title Max Length and Message Max Length to control how content is truncated to fit SMS limits.

Recipient Count

The notification form shows the estimated recipient count based on:

  • Selected channel (Email/SMS/Both)
  • Active subscribers matching that channel

After sending, it shows the actual number of recipients.

Retrying Failed Notifications

If a notification fails:

  1. The error message is displayed in the notification form
  2. The Retry Send button allows you to attempt again
  3. Fix any configuration issues before retrying

Common failure reasons:

  • SMTP not configured
  • Twilio not configured
  • Invalid credentials
  • Network issues

Best Practices

Writing Notifications

  1. Be concise - Get to the point quickly
  2. Include impact - What services are affected?
  3. Set expectations - When will it be resolved?
  4. Provide updates - Keep subscribers informed

Timing

  1. Send promptly - Notify as soon as you’re aware
  2. Update regularly - Post updates at least hourly during incidents
  3. Confirm resolution - Always send a final “resolved” notification

Testing

  1. Create a test subscriber (your email/phone)
  2. Create a test incident
  3. Send the notification to verify delivery
  4. Delete test data when done

Subscribers

Managing Subscribers

Go to Notifications → Subscribers to:

  • View all subscribers
  • Add subscribers manually
  • Deactivate subscribers
  • See subscription type (email/SMS)

Subscription Types

  • Email - Requires valid email address
  • SMS - Requires phone number with country code

Active vs Inactive

  • Active - Will receive notifications
  • Inactive - Opted out or deactivated

Subscribers can unsubscribe via the link in emails, which sets them to inactive.

Automation & Jobs Queue

Notifications are sent via a background jobs queue:

  • Prevents timeouts for large subscriber lists
  • Automatic retries on failure (up to 3 attempts)
  • Progress tracking in the notification status

The queue processes immediately in development and can be scaled with workers in production.

Managing Subscribers

Subscribers receive notifications about incidents and maintenance windows.

Subscription Types

TypeDescription
EmailReceives notifications via email
SMSReceives notifications via text message

Each subscriber has one type. Users who want both should create two subscriptions.

Adding Subscribers

Manual Addition

  1. Go to Notifications → Subscribers
  2. Click Create New
  3. Fill in:
    • Type - Email or SMS
    • Email - Email address (for email type)
    • Phone - Phone number with country code (for SMS type)
    • Verified - Whether the subscription is verified
    • Active - Whether to send notifications
  4. Click Save

Public Subscription

Users can subscribe via the public status page:

  1. Click the “Subscribe” button on the status page
  2. Enter their email or phone number
  3. They appear in the Subscribers list

Subscriber Fields

FieldDescription
TypeEmail or SMS
EmailEmail address (for email subscribers)
PhonePhone number with country code (for SMS)
VerifiedWhether the subscription is verified
ActiveWhether to receive notifications
Verification TokenAuto-generated token for verification
Unsubscribe TokenAuto-generated token for unsubscribe links

Active vs Inactive

  • Active - Subscriber will receive notifications
  • Inactive - Subscriber will NOT receive notifications

Subscribers become inactive when:

  • They click the unsubscribe link in an email
  • An admin manually deactivates them

Unsubscribe Flow

Each email includes an unsubscribe link:

https://status.example.com/unsubscribe/{token}

When clicked:

  1. User sees a confirmation message
  2. Subscription is set to inactive
  3. They no longer receive notifications

The unsubscribe link is unique per subscriber and doesn’t expire.

Phone Number Format

SMS phone numbers must include the country code:

  • +14155551234 (US)
  • +442071234567 (UK)
  • +33123456789 (France)
  • 415-555-1234 (missing country code)
  • (415) 555-1234 (missing country code)

Verification

The Verified field indicates whether the email/phone has been confirmed.

For manually added subscribers, you can set this to true if you’ve verified the contact information.

Bulk Operations

To deactivate multiple subscribers:

  1. Select subscribers in the list view
  2. Use bulk actions to update

Privacy Considerations

  • Store only necessary contact information
  • Provide easy unsubscribe options
  • Respect unsubscribe requests immediately
  • Consider data retention policies

Local Development Setup

This guide explains how to set up Yet Another Status Page for local development.

Prerequisites

  • Node.js 24+
  • PostgreSQL 15+ (or Docker)
  • npm or pnpm

Quick Start with Docker

The easiest way to develop locally is using the included Docker Compose configuration.

# Clone the repository
git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page

# Start the development environment
docker compose -f docker-compose.dev.yml up -d postgres  # Start only the database

# Install dependencies
npm install

# Run database migrations
npm run payload migrate

# Start the development server
npm run dev

Visit:

  • Status page: http://localhost:3333
  • Admin panel: http://localhost:3333/admin

Note: The dev compose file uses port 3333 to avoid conflicts with production on port 3000.

Manual Setup

1. Install PostgreSQL

# macOS with Homebrew
brew install postgresql@16
brew services start postgresql@16

# Create database
createdb hostzero_status

2. Clone and Install

git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page
npm install

3. Configure Environment

cp .env.example .env

Edit .env:

DATABASE_URI=postgres://localhost:5432/hostzero_status
PAYLOAD_SECRET=your-development-secret-key
SERVER_URL=http://localhost:3000

4. Run Migrations

npm run payload migrate

5. Start Development Server

npm run dev

Development Scripts

ScriptDescription
npm run devStart development server with hot reload
npm run buildBuild for production
npm run startStart production server
npm run payload migrateRun database migrations
npm run payload generate:typesGenerate TypeScript types
npm run payload generate:importmapGenerate import map for custom components

Project Structure

status-page/
├── src/
│   ├── app/                    # Next.js App Router
│   │   ├── (frontend)/         # Public status pages
│   │   ├── (payload)/          # Admin panel
│   │   └── api/                # API routes
│   ├── collections/            # Payload CMS collections
│   ├── components/             # React components
│   │   ├── admin/              # Admin panel components
│   │   └── status/             # Status page components
│   ├── globals/                # Payload CMS globals
│   ├── lib/                    # Utility functions
│   └── tasks/                  # Background job handlers
├── public/                     # Static assets
├── payload.config.ts           # Payload CMS configuration
└── tailwind.config.ts          # Tailwind CSS configuration

Making Changes

Adding a Collection

  1. Create a new file in src/collections/
  2. Export the collection config
  3. Import and add to payload.config.ts
  4. Run npm run payload generate:types
  5. Run migrations if needed

Adding a Custom Admin Component

  1. Create component in src/components/admin/
  2. Reference it in the collection config
  3. Run npm run payload generate:importmap

Adding an API Endpoint

  1. Create a route file in src/app/api/
  2. Export GET, POST, etc. handlers

Testing

# Type checking
npm run typecheck

# Build test
npm run build

Debugging

Database Issues

# Connect to database
psql $DATABASE_URI

# Reset database
dropdb hostzero_status && createdb hostzero_status
npm run payload migrate

Clear Cache

rm -rf .next
npm run dev

Docker Compose

Docker Compose is the easiest way to bring up the full stack on a single host without Kubernetes. It is intended for local development, evaluation, and small self-hosted deployments.

For production, use the Helm chart. It handles upgrades, persistence, NetworkPolicy, PDB, Ingress + TLS, and external databases out of the box.

The repository ships three compose files:

FilePurpose
docker-compose.dev.ymlHot-reloading dev environment (Next.js + Postgres). See Local Setup.
docker-compose.test.ymlE2E test environment used by CI.
docker-compose.ymlPre-built image + Postgres for evaluation or single-host self-hosting.

Quick start (single-host)

git clone https://github.com/Hostzero-GmbH/yet-another-status-page.git
cd yet-another-status-page

cp .env.example .env
# Edit .env: set PAYLOAD_SECRET, POSTGRES_PASSWORD, SERVER_URL

docker compose up -d

Then visit:

.env keys

DATABASE_URI=postgres://hostzero:${POSTGRES_PASSWORD}@db:5432/hostzero_status
POSTGRES_PASSWORD=change-me
PAYLOAD_SECRET=$(openssl rand -hex 32)
SERVER_URL=https://status.yourdomain.com

Email (SMTP) and SMS (Twilio) are configured from Configuration → Email/SMS Settings in the admin panel, not via environment variables.

Updating

docker compose pull
docker compose up -d

Backup and restore

Database

docker compose exec db pg_dump -U hostzero hostzero_status > backup.sql
cat backup.sql | docker compose exec -T db psql -U hostzero hostzero_status

Uploads

docker compose cp app:/app/public/media ./media-backup

Reverse proxy

Compose ships only the application container on port 3000. Terminate TLS with the proxy you already operate (nginx, Caddy, Traefik, etc.) and forward to app:3000. A typical Traefik label set:

services:
  app:
    image: ghcr.io/hostzero-gmbh/yet-another-status-page:latest
    environment:
      - DATABASE_URI=${DATABASE_URI}
      - PAYLOAD_SECRET=${PAYLOAD_SECRET}
      - SERVER_URL=https://status.yourdomain.com
    labels:
      - traefik.enable=true
      - traefik.http.routers.status.rule=Host(`status.yourdomain.com`)
      - traefik.http.routers.status.entrypoints=websecure
      - traefik.http.routers.status.tls.certresolver=letsencrypt
      - traefik.http.services.status.loadbalancer.server.port=3000

For anything beyond a single host — multi-replica, rolling upgrades, autoscaling, secret management, NetworkPolicy — switch to the Helm chart.

Architecture

Yet Another Status Page is built with modern technologies for reliability and developer experience.

Tech Stack

ComponentTechnology
FrameworkNext.js 15 (App Router)
CMSPayload CMS 3.x
DatabasePostgreSQL
StylingTailwind CSS
Rich TextLexical Editor

System Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Clients                               │
│  (Browsers, API Consumers, Email Clients, SMS)              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Next.js Application                       │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────┐ │
│  │  Status Pages   │  │   Admin Panel   │  │  REST API   │ │
│  │  (Frontend)     │  │  (Payload CMS)  │  │  Endpoints  │ │
│  └─────────────────┘  └─────────────────┘  └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│                      Payload CMS Core                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐ │
│  │ Collections │  │   Globals   │  │    Jobs Queue       │ │
│  └─────────────┘  └─────────────┘  └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                      PostgreSQL                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   External Services                          │
│  ┌─────────────────┐           ┌─────────────────┐         │
│  │   SMTP Server   │           │     Twilio      │         │
│  │   (Email)       │           │     (SMS)       │         │
│  └─────────────────┘           └─────────────────┘         │
└─────────────────────────────────────────────────────────────┘

Data Model

Collections

┌─────────────────┐     ┌─────────────────┐
│  ServiceGroups  │────▶│    Services     │
│  - name         │     │  - name         │
│  - description  │     │  - status       │
│  - order        │     │  - group        │
└─────────────────┘     └─────────────────┘
                              │
                    ┌─────────┴─────────┐
                    ▼                   ▼
           ┌─────────────────┐  ┌─────────────────┐
           │   Incidents     │  │  Maintenances   │
           │  - title        │  │  - title        │
           │  - status       │  │  - status       │
           │  - impact       │  │  - schedule     │
           │  - updates[]    │  │  - updates[]    │
           └─────────────────┘  └─────────────────┘
                    │                   │
                    └─────────┬─────────┘
                              ▼
                    ┌─────────────────┐
                    │  Notifications  │
                    │  - title        │
                    │  - channel      │
                    │  - status       │
                    │  - content      │
                    └─────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   Subscribers   │
                    │  - type         │
                    │  - email/phone  │
                    │  - active       │
                    └─────────────────┘

Globals

  • Settings - Site configuration, SMTP, Twilio credentials

Request Flow

Status Page Request

Browser → Next.js → Server Component → Payload API → PostgreSQL
                          ↓
                    Rendered HTML

Admin Panel Request

Browser → Next.js → Payload Admin → Payload API → PostgreSQL
                          ↓
                    React SPA

Notification Flow

Save Incident → afterChange Hook → Create Notification Draft
                                          ↓
                              Admin Reviews Draft
                                          ↓
                              Click "Send Now"
                                          ↓
                              API Queues Job
                                          ↓
                              Jobs Queue Processes
                                          ↓
                              SMTP/Twilio Sends
                                          ↓
                              Update Notification Status

Key Design Decisions

Why Payload CMS?

  • Modern, TypeScript-first CMS
  • Excellent admin UI out of the box
  • Flexible data modeling
  • Built-in authentication
  • Jobs queue for background tasks

Why Next.js App Router?

  • Server components for performance
  • Streaming and suspense support
  • Built-in API routes
  • Excellent developer experience

Why PostgreSQL?

  • Robust and reliable
  • Excellent JSON support
  • Widely supported
  • Easy to backup and scale

Why Separate Notifications Collection?

  • Audit trail of all notifications
  • Review before sending
  • Retry failed notifications
  • Clear status tracking

Scaling Considerations

Horizontal Scaling

  • Application is stateless
  • Can run multiple instances behind load balancer
  • Shared PostgreSQL database

Database

  • Connection pooling (PgBouncer)
  • Read replicas for high traffic
  • Regular backups

Jobs Queue

  • Can add dedicated worker processes
  • Automatic retries on failure
  • Scales with subscriber count

REST API

Yet Another Status Page provides a REST API for programmatic access to status data.

Base URL

https://your-status-page.com/api

Authentication

Most read endpoints are public. Admin endpoints require authentication via:

  • Session cookie (from admin login)
  • API key header: Authorization: Bearer <api-key>

Endpoints

Incidents

List Incidents

GET /api/incidents

Query parameters:

ParameterDescription
limitNumber of results (default: 10)
pagePage number (default: 1)
where[status][equals]Filter by status

Get Incident

GET /api/incidents/:id

Maintenances

List Maintenances

GET /api/maintenances

Query parameters same as incidents.

Get Maintenance

GET /api/maintenances/:id

Services

List Services

GET /api/services

Get Service

GET /api/services/:id

Service Groups

List Service Groups

GET /api/service-groups

Get Service Group

GET /api/service-groups/:id

Subscribers

Subscribe

POST /api/subscribe
Content-Type: application/json

{
  "type": "email",
  "email": "user@example.com"
}

Response:

{
  "success": true,
  "message": "Subscription successful"
}

Unsubscribe

POST /api/unsubscribe
Content-Type: application/json

{
  "token": "unsubscribe-token"
}

Payload REST API

The full Payload REST API is available at /api. See the Payload documentation for complete details.

Common Patterns

Filtering

GET /api/incidents?where[status][equals]=investigating

Sorting

GET /api/incidents?sort=-createdAt

Pagination

GET /api/incidents?limit=10&page=2

Field Selection

GET /api/incidents?select[title]=true&select[status]=true

GraphQL

A GraphQL endpoint is available at:

POST /api/graphql

GraphQL Playground (development only):

GET /api/graphql-playground

Rate Limiting

Public endpoints are rate limited to prevent abuse:

  • 100 requests per minute per IP for read endpoints
  • 10 requests per minute per IP for subscribe endpoint

Error Responses

All errors follow this format:

{
  "errors": [
    {
      "message": "Error description"
    }
  ]
}

Common HTTP status codes:

CodeDescription
200Success
400Bad request
401Unauthorized
404Not found
429Rate limited
500Server error