Git, CI/CD & GitOpsLXXVIII · FluxNotification
Notification controller and webhooks — the alerting layer
What you'll learn
- Describe what notification-controller does and which CRDs it owns
- Configure a Provider for Slack, Teams, Discord, or generic webhook
- Configure an Alert that maps a source event to a receiver
- Configure a Receiver and expose the webhook endpoint for inbound Git host events
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
notification-controller is the alerting layer of Flux. It owns
three CRDs - Provider, Alert, and Receiver - that turn
Flux events into Slack messages, Teams webhooks, Discord
posts, or generic HTTP callbacks. The same controller exposes
an inbound webhook endpoint that Git hosts call to trigger an
immediate reconcile.
What notification-controller does
The controller has two faces:
- Outbound. Receives events from source-controller,
kustomize-controller, and helm-controller. Matches each
event against an
Alert, identifies aProvider, fans the message out. - Inbound. Exposes an HTTP endpoint (
/hook/:guid) that Git hosts call on push. The endpoint writes an annotation that forces the next reconcile out of cycle.
flowchart LR
SC["source-controller"] --> EV["events"]
KC["kustomize-controller"] --> EV
HC["helm-controller"] --> EV
EV --> NC["notification-controller"]
NC --> AL["Alert: matches event"]
AL --> P["Provider"]
P --> Slack["Slack"]
P --> Teams["Teams"]
P --> Discord["Discord"]
P --> WH["Generic webhook"]
GH["Git host"] -->|"/hook/:guid"| NC
NC -->|"annotation"| GR["GitRepository / Kustomization"]
The outbound path replaces polling flux get kustomizations;
the inbound path replaces waiting for the source interval.
The three CRDs
Provider declares a destination and its credentials:
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: prod-deploys
address: https://slack.example.com
secretRef:
name: slack-webhook
The provider type determines the message format and required
credentials. slack, msteams, discord, github,
gitlab, generic are supported.
Alert declares which events go where:
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
name: on-call
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: error
eventSources:
- kind: Kustomization
name: '*'
exclusionList:
- ".*pulled.*new.*image.*"
The alert matches every error event from every Kustomization
and routes it to the slack provider. The exclusionList
drops matching messages.
Receiver declares an inbound webhook endpoint and the
resource it annotates:
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: github
namespace: flux-system
spec:
type: github
events:
- "ping"
secretRef:
name: github-webhook-secret
resources:
- apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
name: apps
The receiver is exposed at
http://notification-controller.flux-system.svc.cluster.local:8080/hook/$GUID.
The Git host sends a ping (or push) event to that URL with
the secret. The controller authenticates the call, identifies
the resources in spec.resources, and writes a reconcile
annotation on each.
End-to-end: source-controller to Slack
sequenceDiagram
participant SC as source-controller
participant NC as notification-controller
participant A as Alert
participant P as Provider
participant Slack as Slack
SC->>NC: error event
NC->>A: match event
A->>P: send to slack provider
P->>Slack: POST /services/.../incoming-webhook
Slack-->>NC: 200 OK
The controller writes every event to its audit log regardless
of whether an Alert matched. flux logs against the
notification-controller namespace shows every event.
Under the hood
The controller is small because the work is mostly protocol translation. Outbound: format the event as Slack JSON or generic webhook JSON. Inbound: validate the HMAC signature, identify the resources, write annotations.
Production discipline
eventSeverity: erroris the production default for Alert. Info events flood Slack; errors need a human eye.exclusionListis curated, not empty. A noisy stream is one operators mute; a missing event is one nobody catches.- The Receiver URL is exposed through an ingress with TLS and an allowlist. No public webhook without a source IP allowlist.
Cross-course references
- Kubernetes for Production Sysadmins - Part XXIII (Ingress and TLS) covers the ingress the Receiver URL terminates at.
- Git, CI/CD & GitOps for Infrastructure Engineers - Part LXXV (Pull-based Deployment) is the pattern this controller implements.
Quiz
Knowledge check · 4 questions
Q1. Which CRD declares the destination endpoint (Slack, Teams, Discord, or generic webhook) that an Alert routes an event to?
Q2. A Receiver's webhook URL is an in-cluster service that a public Git host can reach without additional ingress configuration.
Q3. Name the three CRDs notification-controller owns and identify which one declares an inbound webhook endpoint that a Git host calls.
Q4. Diagnose why the team is seeing Slack alerts for successful reconciles but missing alerts for failed reconciles, and recommend the fix.
A team has a single Alert in the flux-system namespace with eventSeverity: info and no exclusionList. The Slack channel receives a flood of messages for every successful reconcile (Ready=True transitions) but the team has not seen a single error alert in two weeks, even though two Kustomizations have been failing with `DependencyNotReady`. The Alert's providerRef points to a working slack Provider.
Passing score: 75%. Answers are checked in this browser.