Skip to content

feat(observability): env-driven Slack/Discord/webhook Alertmanager receivers - #48

Merged
agjs merged 1 commit into
mainfrom
feat/alertmanager-receivers
May 29, 2026
Merged

feat(observability): env-driven Slack/Discord/webhook Alertmanager receivers#48
agjs merged 1 commit into
mainfrom
feat/alertmanager-receivers

Conversation

@agjs

@agjs agjs commented May 29, 2026

Copy link
Copy Markdown
Contributor

The default observability stack already shipped 14 Prometheus alert
rules (API 5xx + latency, Postgres health, disk/memory/CPU pressure,
Traefik down), but Alertmanager's default receiver was an empty
stub — so alerts fired into the Alertmanager UI and nowhere else.
This wires real notification delivery for the three formats people
actually ask for.

Approach

Alertmanager has no native env-var substitution in its config, so a
small entrypoint.sh renders alertmanager.yml from ALERTMANAGER_*
env vars at container boot, then execs the real Alertmanager binary.
The receiver block is built conditionally:

• ALERTMANAGER_SLACK_WEBHOOK_URL set → slack_configs block emitted
with a readable Slack message template (severity + component +
summary + description). Same env handles Discord because Discord
accepts Slack-format payloads at the /slack suffix — paste a
Discord webhook URL with /slack appended and it just works.
• ALERTMANAGER_WEBHOOK_URL set → webhook_configs block emitted
for operators who want Alertmanager's native JSON (custom bridges,
n8n, PagerDuty events-v2 translators, etc.).
• Both can be set; alerts fan out to both.
• Neither set → receiver has no notifiers; alerts surface in the
Alertmanager UI at :9093 only. Still a valid config — verified
with amtool check-config.

Routing already supports severity=page (1h re-notify, urgent) and
severity=warn (12h re-notify, no pager-spam) via the existing rule
labels — the new alertmanager.yml header preserves that.

Files

  • compose/alertmanager/entrypoint.sh (new) — renders + execs.
  • compose/alertmanager/alertmanager.yml (deleted) — static file
    superseded by the env-driven render.
  • compose/docker-compose.observability.yml — alertmanager service now
    uses entrypoint, declares ALERTMANAGER_* env vars, mounts the
    script in place of the static config.
  • compose/.env.example — documents the new env vars + the Discord
    /slack-suffix trick.
  • infra/compose/docs/alerts.md (new) — full walkthrough: which rules
    ship, Slack setup, Discord setup, generic webhook setup, tuning
    thresholds, adding rules, end-to-end ping test.
  • infra/compose/docs/observability.md — old "Adding alert rules"
    paragraph replaced with an Alerts section pointing at alerts.md.

Verification

  • STACK=dev ./dev.sh config --quiet exits 0 with the new compose.
  • shellcheck -x -S warning clean on entrypoint.sh.
  • amtool check-config passes against the rendered config in both the
    both-URLs case (slack_configs + webhook_configs both emitted) and
    the no-URLs case (bare - name: default receiver, alerts UI-only).

Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com

…ceivers

The default observability stack already shipped 14 Prometheus alert
rules (API 5xx + latency, Postgres health, disk/memory/CPU pressure,
Traefik down), but Alertmanager's `default` receiver was an empty
stub — so alerts fired into the Alertmanager UI and nowhere else.
This wires real notification delivery for the three formats people
actually ask for.

Approach
--------
Alertmanager has no native env-var substitution in its config, so a
small `entrypoint.sh` renders alertmanager.yml from `ALERTMANAGER_*`
env vars at container boot, then execs the real Alertmanager binary.
The receiver block is built conditionally:

  • ALERTMANAGER_SLACK_WEBHOOK_URL set → `slack_configs` block emitted
    with a readable Slack message template (severity + component +
    summary + description). Same env handles Discord because Discord
    accepts Slack-format payloads at the `/slack` suffix — paste a
    Discord webhook URL with `/slack` appended and it just works.
  • ALERTMANAGER_WEBHOOK_URL set → `webhook_configs` block emitted
    for operators who want Alertmanager's native JSON (custom bridges,
    n8n, PagerDuty events-v2 translators, etc.).
  • Both can be set; alerts fan out to both.
  • Neither set → receiver has no notifiers; alerts surface in the
    Alertmanager UI at :9093 only. Still a valid config — verified
    with `amtool check-config`.

Routing already supports `severity=page` (1h re-notify, urgent) and
`severity=warn` (12h re-notify, no pager-spam) via the existing rule
labels — the new alertmanager.yml header preserves that.

Files
-----
- compose/alertmanager/entrypoint.sh (new) — renders + execs.
- compose/alertmanager/alertmanager.yml (deleted) — static file
  superseded by the env-driven render.
- compose/docker-compose.observability.yml — alertmanager service now
  uses entrypoint, declares ALERTMANAGER_* env vars, mounts the
  script in place of the static config.
- compose/.env.example — documents the new env vars + the Discord
  /slack-suffix trick.
- infra/compose/docs/alerts.md (new) — full walkthrough: which rules
  ship, Slack setup, Discord setup, generic webhook setup, tuning
  thresholds, adding rules, end-to-end ping test.
- infra/compose/docs/observability.md — old "Adding alert rules"
  paragraph replaced with an Alerts section pointing at alerts.md.

Verification
------------
- `STACK=dev ./dev.sh config --quiet` exits 0 with the new compose.
- `shellcheck -x -S warning` clean on entrypoint.sh.
- amtool check-config passes against the rendered config in both the
  both-URLs case (slack_configs + webhook_configs both emitted) and
  the no-URLs case (bare `- name: default` receiver, alerts UI-only).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@agjs
agjs enabled auto-merge (squash) May 29, 2026 09:55
@agjs
agjs merged commit 1804744 into main May 29, 2026
23 checks passed
@agjs
agjs deleted the feat/alertmanager-receivers branch May 29, 2026 09:59
agjs added a commit that referenced this pull request May 29, 2026
Catches the operator-facing docs site up with all the infra and
correlation work that landed across PRs #45, #46, #47, and #48 —
defaults flip + dashboards + structured logging + user/trace
correlation + env-driven alert receivers.

topics/observability.mdx
- "What ships" — five dashboards (was one); Pino pipeline detail;
  Alertmanager wired with env-driven receivers (was "you wire it
  yourself").
- "Default dashboards" — full per-dashboard breakdown (API, API
  logs, UI logs, Postgres, Host) with what each is best at and a
  pointer to the click-through data links on trace_id / userId /
  requestId.
- "Design choices" — added "on by default", trace_id flow, and
  env-driven Alertmanager entries; dropped the now-misleading
  "Prometheus + Loki, not OpenTelemetry" claim (the Sentry tracing
  side does carry traces).
- "Querying" — LogQL examples now use the `level` label (was the
  broken `level=ERROR` substring match) and include trace_id /
  userId pivot queries.
- "Adding an alert" — points at the single bundled rules.yml and
  the new Alerts topic.

topics/error-tracking.mdx
- New "Correlation: GlitchTip ↔ Loki ↔ requestId" section with a
  mermaid diagram of the trace_id flow (browser → API → Loki +
  GlitchTip), a description of what flows automatically (trace_id /
  span_id / userId / requestId), and the three click-through paths
  (Grafana → GlitchTip via dashboard data links, Grafana → Grafana
  via the requestId link, GlitchTip → Grafana via the operator-side
  one-time tag link template).
- Design-choices block updated with on-by-default GlitchTip and
  the user.id / trace_id tagging.

topics/alerts.mdx (new)
- New topic page promoting the alerts walkthrough to the user-facing
  docs site. Covers what the 14 default rules fire on, the three
  receiver formats (Slack, Discord-via-/slack-suffix, generic
  webhook), an end-to-end curl one-liner to ping a fake alert, how
  to add rules, and the cost-of-pager-fatigue note.

reference/env-vars.mdx
- New entries for ALERTMANAGER_SLACK_WEBHOOK_URL,
  ALERTMANAGER_SLACK_CHANNEL, ALERTMANAGER_WEBHOOK_URL.

reference/commands.mdx
- "Hot-reload Prometheus rules" — `curl POST /-/reload`.
- "Ping a fake alert (verify receiver wiring)" — the verification
  one-liner from the new Alerts topic.

astro.config.mjs
- Wire the new Alerts topic into the Topics sidebar between
  Observability and Provisioning with OpenTofu.

Verification: `bun run build` clean, 66 pages built (was 65, now +1
for Alerts), pagefind index built without errors.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant