Build Instatus

KINDAback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A status page and incident communication tool where the interesting constraints are all availability and honesty: independent infrastructure, a page that degrades to static, notifications that go out through a path unaffected by your outage, and a subscriber list that is a liability if handled carelessly.

estimated effort weekendthe files for this build are in the project pack

HostingStatic generation to object storage with a CDN, on a separate accountAuthoringA small application, deliberately elsewhere, that publishes the static pageChecksExternal probes from a third party or a separate regionNotificationsEmail and webhooks through a provider unrelated to your productDataA small database for the authoring side only

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the authoring application and the static generator.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. pennies per month for static hosting

    Why This is the whole build. A status page on the same account as the product fails at exactly the moment it is needed.

    Get it A second account with a different provider if possible, with its own billing so a payment problem cannot take down both.

    Verify A written list shows no shared dependency between the page and the product

  3. have readythe price of a domain, or free for a delegated subdomain

    Why Sharing a DNS provider with the product means a DNS incident removes both, and DNS incidents are a common cause of total outages.

    Get it A subdomain delegated to a different provider, or a separate domain entirely.

    Verify Resolving the status hostname does not depend on the product's DNS provider

  4. free tiers cover a small subscriber list

    Why Notifying subscribers through the mail infrastructure that is currently broken sends nothing.

    Get it Any transactional email provider, with credentials stored somewhere retrievable during an outage.

    Verify A test notification sends and the credentials are retrievable without the product being up

  5. decidefree

    Why During an incident nobody has time to decide whether this is degraded or partial, and inconsistent statuses are what make a page untrustworthy.

    Get it Write one sentence defining each status, and name who may post. Do it before the first incident, not during it.

    Verify A written definition exists for every status level and the page links to it

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- components(id, name, description, ordinal, current_status) · status is operational, degraded, partial or major
- incidents(id, title, impact, started_at, resolved_at, created_by)
- updates(id, incident_id, status, body, at) · investigating, identified, monitoring or resolved. Append only
- incident_components(incident_id, component_id, impact)
- maintenance(id, title, scheduled_start, scheduled_end, body, notified_at)
- subscribers(id, channel, address_hash, address_encrypted, confirmed_at, unsubscribed_at) · a list of your customers, so treated as sensitive
- publishes(id, at, revision, source) · every publish of the static page, so you can prove what it said and when
- Updates are append only, because editing what a status page said during an incident is how a company loses the benefit of having one.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/statusAuthoring side only. The published page does not read it, which is the point.
PUBLISH_BUCKETrequireds3://status-example-comWhere the static page is published. On the separate account.
PUBLISH_CREDENTIALSsecretrequireda key for the separate accountScoped to the bucket only, and belonging to the account that shares nothing with the product.
NOTIFY_PROVIDER_KEYsecretoptionala provider keyThe unrelated notification provider. Store it where you can reach it during an outage.
CHECK_FAILURES_BEFORE_INCIDENTrequired3Consecutive failures before automation opens an incident. One failure is noise.
NOTIFY_RATE_LIMIT_MINUTESrequired15Minimum gap between notifications for one component, so flapping does not spam.

The build, in order

  1. A page that is nearly impossible to take down

    The status page is served as static files from infrastructure you share with nothing.

    1. No application server in the read path. Almost nothing left to break.

      Files src/publish.ts

    2. Write down every shared dependency and remove the ones you can.

      Files docs/independence.md

    3. The page must keep serving. Test it rather than assuming it.

      Files tests/independence.md

    done when · tick each as it passes
    watch out
    • Hosting the status page in the same account, region or cluster as the product means a provider incident takes out both, which is the single most common way status pages fail their one job. Separate accounts at minimum, and a different provider if you can accept the extra work.
  2. Incidents, written once and never rewritten

    An incident is posted, updated as it develops, and the history is permanent.

    1. Enforced in the schema, not by convention.

      Files src/incidents.ts

    2. An incident update that has not published has not happened.

      Files src/publish.ts

    3. The history is the point. Hiding it is hiding your record.

      Files src/history.ts

    done when · tick each as it passes
    watch out
    • Editing a previous update to soften what was said, or quietly changing a start time, destroys the only thing a status page is actually for, which is being a record people can trust. Append only, with the correction visible as a correction.
  3. Checks that can see what customers see

    Automated probes run from outside and can open an incident but never close one.

    1. A health endpoint returning true is not what customers experience.

      Files src/checks.ts

    2. One failure is noise. Acting on it produces a page nobody trusts.

      Files src/checks.ts

    3. Only a person closes an incident. A premature all-clear is worse than the outage.

      Files src/automation.tssrc/automation.test.ts

    done when · tick each as it passes
    watch out
    • A check running inside your own infrastructure passes happily during an outage that affects everyone outside it, which is the exact scenario you needed to detect. Probe from somewhere that shares nothing with the system, and treat a single failure as noise rather than an incident.
  4. Telling people, through a path that still works

    Subscribers hear about the incident even though your product is down.

    1. With credentials retrievable without your product being up.

      Files src/notify.ts

    2. A subscriber list is a list of your customers. Treat it accordingly.

      Files src/subscribers.ts

    3. A flapping check must not send twenty emails. That is how people unsubscribe.

      Files src/ratelimit.ts

    done when · tick each as it passes
    watch out
    • Sending notifications through the same mail infrastructure your product uses means an outage that includes that infrastructure silences exactly the notification you needed to send. Use a separate provider, and hold the credentials somewhere that does not depend on your product being up to retrieve.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more uptime.