|
| 1 | +--- |
| 2 | +title: Alerts |
| 3 | +description: 14 default Prometheus rules + env-driven Slack, Discord, and webhook receivers in Alertmanager. Set one env var to get pager output, none to keep alerts in the UI. |
| 4 | +--- |
| 5 | + |
| 6 | +import PageIntro from "../../../components/docs-kit/PageIntro"; |
| 7 | +import FaqGroup from "../../../components/FaqGroup.tsx"; |
| 8 | +import FaqItem from "../../../components/FaqItem.tsx"; |
| 9 | +import DocCallout from "../../../components/DocCallout.tsx"; |
| 10 | + |
| 11 | +<PageIntro |
| 12 | + eyebrow="Alerts" |
| 13 | + actions={[ |
| 14 | + { label: "What fires", href: "#what-rules-ship" }, |
| 15 | + { label: "Receivers", href: "#wiring-a-receiver" }, |
| 16 | + ]} |
| 17 | + facts={[ |
| 18 | + { value: "14", label: "default rules" }, |
| 19 | + { value: "3", label: "receiver formats" }, |
| 20 | + { value: "1", label: "env var to wire" }, |
| 21 | + ]} |
| 22 | +> |
| 23 | + Prometheus alert rules + Alertmanager receivers ship with the |
| 24 | + observability stack. Set `ALERTMANAGER_SLACK_WEBHOOK_URL` in |
| 25 | + `compose/.env` and alerts deliver to Slack, Discord, or any |
| 26 | + Slack-compatible endpoint. Without it, alerts surface in the |
| 27 | + Alertmanager UI at `:9093` only — useful for confirming rules fire |
| 28 | + before committing to a pager destination. |
| 29 | +</PageIntro> |
| 30 | + |
| 31 | +## What rules ship |
| 32 | + |
| 33 | +`compose/prometheus/rules.yml` defines four rule groups covering the |
| 34 | +obvious failure modes. Severity labels drive Alertmanager's re-notify |
| 35 | +cadence: `severity=page` repeats hourly, `severity=warn` every 12 |
| 36 | +hours. |
| 37 | + |
| 38 | +<FaqGroup> |
| 39 | + <FaqItem title="boringstack-api" open> |
| 40 | + `ApiServerErrorsHigh` (page, 5xx > 1% for 5min), |
| 41 | + `ApiServerErrorsCritical` (page, 5xx > 5% for 2min), |
| 42 | + `ApiLatencyP95High` (warn, p95 > 1s for 10min), |
| 43 | + `ApiUnreachable` (page, no API traffic for 5min). |
| 44 | + </FaqItem> |
| 45 | + <FaqItem title="boringstack-database"> |
| 46 | + `PostgresDown` (page, exporter can't reach pg for 2min), |
| 47 | + `PostgresConnectionsHigh` (warn, > 80% of `max_connections` for 5min), |
| 48 | + `PostgresReplicationLagHigh` (warn, lag > 60s for 5min). |
| 49 | + </FaqItem> |
| 50 | + <FaqItem title="boringstack-host"> |
| 51 | + `DiskSpaceLow` (warn, < 10% free for 5min), |
| 52 | + `DiskSpaceCritical` (page, < 5% free for 2min), |
| 53 | + `MemoryPressureHigh` (warn, available < 10% for 10min), |
| 54 | + `HostCpuSaturated` (warn, load5/cores > 1.5 for 15min), |
| 55 | + `NodeExporterDown` (warn, host metrics stale for 3min). |
| 56 | + </FaqItem> |
| 57 | + <FaqItem title="boringstack-edge"> |
| 58 | + `TraefikDown` (page, Traefik metrics endpoint unreachable for 2min) — |
| 59 | + Traefik is the only path into the stack in prod; this firing means |
| 60 | + the site is down. |
| 61 | + </FaqItem> |
| 62 | +</FaqGroup> |
| 63 | + |
| 64 | +Thresholds are conservative defaults for a single-host BoringStack |
| 65 | +deployment. Tune per workload — a busy app may legitimately push |
| 66 | +4xx/5xx absolute counts above what a quiet baseline catches. |
| 67 | + |
| 68 | +## Wiring a receiver |
| 69 | + |
| 70 | +Alertmanager has no native env-var substitution in its config, so the |
| 71 | +stack ships a small `entrypoint.sh` that renders alertmanager.yml |
| 72 | +from `ALERTMANAGER_*` env vars at container boot. Empty env → that |
| 73 | +block is omitted entirely, so `amtool check-config` stays happy. |
| 74 | + |
| 75 | +<FaqGroup> |
| 76 | + <FaqItem title="Slack" open> |
| 77 | + Create a Slack app + Incoming Webhook |
| 78 | + ([guide](https://api.slack.com/messaging/webhooks)). Copy the URL, |
| 79 | + then in `compose/.env`: |
| 80 | + ```env |
| 81 | + ALERTMANAGER_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../... |
| 82 | + ALERTMANAGER_SLACK_CHANNEL=#alerts |
| 83 | + ``` |
| 84 | + Restart: `docker compose restart alertmanager`. Default message |
| 85 | + template renders status, severity, component, summary, description. |
| 86 | + </FaqItem> |
| 87 | + <FaqItem title="Discord"> |
| 88 | + Discord accepts Slack-format payloads at a `/slack` suffix on its |
| 89 | + webhook URL — so the **same env var** delivers there: |
| 90 | + 1. In Discord: `Server Settings → Integrations → Webhooks → New |
| 91 | + Webhook`. Copy the URL. |
| 92 | + 2. **Append `/slack`**: `https://discord.com/api/webhooks/.../slack`. |
| 93 | + 3. Set `ALERTMANAGER_SLACK_WEBHOOK_URL` to that value in |
| 94 | + `compose/.env`. `ALERTMANAGER_SLACK_CHANNEL` is ignored — the |
| 95 | + channel is fixed when the webhook is created. |
| 96 | + 4. Restart Alertmanager. |
| 97 | + </FaqItem> |
| 98 | + <FaqItem title="Generic webhook (Alertmanager JSON)"> |
| 99 | + For custom bridges (n8n, your own alerter, PagerDuty events-v2 |
| 100 | + translators), use the generic receiver. It POSTs Alertmanager's |
| 101 | + native JSON — different from Slack's format. |
| 102 | + ```env |
| 103 | + ALERTMANAGER_WEBHOOK_URL=https://your-bridge.example.com/alerts |
| 104 | + ``` |
| 105 | + Can be set in addition to the Slack one; alerts fan out to both. |
| 106 | + </FaqItem> |
| 107 | + <FaqItem title="None (UI only)"> |
| 108 | + Leave both env vars unset. The 14 rules still fire — they land in |
| 109 | + the Alertmanager UI at `http://localhost:9093`. Useful for seeing |
| 110 | + what alerts look like before deciding where to pager them. |
| 111 | + </FaqItem> |
| 112 | +</FaqGroup> |
| 113 | + |
| 114 | +## Verifying receivers work |
| 115 | + |
| 116 | +Curl a fake firing alert into Alertmanager's API directly, no waiting |
| 117 | +for a real incident: |
| 118 | + |
| 119 | +```bash |
| 120 | +curl -XPOST http://localhost:9093/api/v2/alerts \ |
| 121 | + -H 'Content-Type: application/json' \ |
| 122 | + -d '[{ |
| 123 | + "labels": { |
| 124 | + "alertname": "TestPing", |
| 125 | + "severity": "warn", |
| 126 | + "component": "manual-test" |
| 127 | + }, |
| 128 | + "annotations": { |
| 129 | + "summary": "Manual ping from the alerts docs", |
| 130 | + "description": "If this lands in your Slack/Discord/webhook, the receiver is wired correctly." |
| 131 | + } |
| 132 | + }]' |
| 133 | +``` |
| 134 | + |
| 135 | +The alert auto-resolves after `resolve_timeout` (5 minutes) since no |
| 136 | +follow-up ping keeps it firing. |
| 137 | + |
| 138 | +## Adding rules |
| 139 | + |
| 140 | +Drop new entries into `compose/prometheus/rules.yml` under an |
| 141 | +existing group (or create a new group). Required fields per alert: |
| 142 | +`alert`, `expr`, `for` (optional debounce duration), `labels.severity` |
| 143 | +(`page` or `warn`), `annotations.summary`. The 14 bundled rules are |
| 144 | +worked examples. |
| 145 | + |
| 146 | +Hot-reload Prometheus without a restart: |
| 147 | + |
| 148 | +```bash |
| 149 | +curl -X POST http://localhost:9090/-/reload |
| 150 | +``` |
| 151 | + |
| 152 | +## Adding routes |
| 153 | + |
| 154 | +Out of the box every alert lands in the single `default` receiver. To |
| 155 | +route specific labels to different receivers (e.g. `component=database` |
| 156 | +to a DBA channel), edit `compose/alertmanager/entrypoint.sh` directly — |
| 157 | +it's a small shell script with `cat >> "$CFG" <<EOF` blocks for each |
| 158 | +receiver. Append your own routes block + matching receivers. |
| 159 | + |
| 160 | +If the routing grows beyond a handful of cases, graduate to a static |
| 161 | +`alertmanager.yml` you maintain yourself and remove the entrypoint |
| 162 | +script. |
| 163 | + |
| 164 | +<DocCallout |
| 165 | + variant="tip" |
| 166 | + title="Pager fatigue is a real cost" |
| 167 | + eyebrow="Tuning thresholds" |
| 168 | +> |
| 169 | + The default `for:` durations are deliberately generous to avoid |
| 170 | + 3am pages on transient blips. If you find you're regularly silencing |
| 171 | + the same alert, the threshold is wrong — either raise the duration, |
| 172 | + the rate threshold, or both. An alert that fires and nobody acts on |
| 173 | + is worse than no alert. |
| 174 | +</DocCallout> |
| 175 | + |
| 176 | +## Source |
| 177 | + |
| 178 | +- [`compose/prometheus/rules.yml`](https://github.com/boringstack-xyz/boringstack/blob/main/infra/compose/compose/prometheus/rules.yml) — the 14 rules. |
| 179 | +- [`compose/alertmanager/entrypoint.sh`](https://github.com/boringstack-xyz/boringstack/blob/main/infra/compose/compose/alertmanager/entrypoint.sh) — env-driven config renderer. |
| 180 | +- [`infra/compose/docs/alerts.md`](https://github.com/boringstack-xyz/boringstack/blob/main/infra/compose/docs/alerts.md) — the operator-side runbook. |
| 181 | + |
| 182 | +## Related |
| 183 | + |
| 184 | +- [Observability](/topics/observability/) — the metrics + logs stack |
| 185 | + these alerts run inside of. |
| 186 | +- [Error tracking](/topics/error-tracking/) — Sentry/GlitchTip catches |
| 187 | + exceptions; Alertmanager catches "metrics-shaped" failures (rates, |
| 188 | + resource pressure, unreachability). Different surfaces, same goal. |
0 commit comments