Provision Alert Rules, Contact Points, and Notification Policies
Define the whole alerting pipeline in YAML so it is reviewed and versioned like code.
Grafana alerting has four moving parts: alert rules decide when something is wrong, contact points say where to send a notification, notification policies route alerts to contact points, and mute timings and templates shape when and how messages go out. All four can be provisioned from YAML in the provisioning/alerting folder, which makes alerting reviewable instead of a pile of UI settings. This guide builds a small working pipeline.
Provisioned alerting resources can’t be edited in the UI, and importing a resource that already exists causes a conflict, so remove hand-made duplicates first. Every file starts with apiVersion: 1.
Contact points
provisioning/alerting/contact-points.yaml:
apiVersion: 1
contactPoints:
- orgId: 1
name: team-slack
receivers:
- uid: team-slack-1
type: slack
disableResolveMessage: false
settings:
url: ${SLACK_WEBHOOK_URL}
recipient: '#alerts'
- orgId: 1
name: oncall-email
receivers:
- uid: oncall-email-1
type: email
settings:
addresses: oncall@example.com
Notes:
uidon each receiver must be unique and stable. Grafana uses it to update the receiver in place.typeis the integration, such asslack,email,webhook,pagerduty,opsgenie, orprometheus-alertmanager. The keys undersettingsdepend on the type, so check the documentation for yours.- Secrets come from environment variables with
$VARor${VAR}, so the webhook URL never lives in Git. Use$$to keep a literal dollar sign. - To remove one, add
deleteContactPointswith itsorgIdanduid.
Notification policies
The policy tree decides which contact point handles which alert. The whole tree is one resource, and provisioning it overwrites all existing policies.
provisioning/alerting/policies.yaml:
apiVersion: 1
policies:
- orgId: 1
receiver: team-slack # default for anything not matched below
group_by: ['alertname', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- receiver: oncall-email
object_matchers:
- ['severity', '=', 'critical']
repeat_interval: 1h
- receiver: team-slack
object_matchers:
- ['team', '=', 'platform']
mute_time_intervals:
- weekends
receiverat the top is the fallback contact point.group_bybatches related alerts into one notification.group_wait,group_interval,repeat_intervalcontrol how quickly the first message goes out, how often updates are sent for a group, and how often an unresolved alert is repeated.routesare child policies using the same fields. The first matching route wins unless you setcontinue: true.object_matchersare[label, operator, value]triples. The operators are=,!=,=~, and!~.
To reset the tree to Grafana’s default, use resetPolicies: [1] with the organization ID.
Mute timings
apiVersion: 1
muteTimes:
- orgId: 1
name: weekends
time_intervals:
- weekdays: ['saturday', 'sunday']
location: 'UTC'
times (with start_time and end_time), weekdays, months, years, and days_of_month can be combined, and ranges use colons, such as 'monday:friday'. Delete with deleteMuteTimes. Mute timings can’t use variable interpolation in their names or intervals.
Notification templates
apiVersion: 1
templates:
- orgId: 1
name: slack_title
template: |
{{ define "slack_title" }}
[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}
{{ end }}
Reference a template from a contact point’s settings (for example the Slack title field) as {{ template "slack_title" . }}. Template names and content don’t support variable interpolation, so write them literally.
Alert rules
Rules are organized in groups that share an evaluation interval and live in a folder. Each rule has a list of query and expression steps, and a condition naming the step that decides whether it fires.
provisioning/alerting/rules.yaml:
apiVersion: 1
groups:
- orgId: 1
name: webapp
folder: Applications
interval: 1m
rules:
- uid: webapp-down
title: Webapp targets down
condition: C
for: 5m
noDataState: NoData
execErrState: Error
labels:
severity: critical
team: platform
annotations:
summary: 'No healthy webapp targets for 5 minutes'
runbook_url: https://runbooks.example.com/webapp-down
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
refId: A
expr: sum(up{job="webapp"})
instant: true
- refId: B
datasourceUid: __expr__
model:
refId: B
type: reduce
expression: A
reducer: last
datasource:
type: __expr__
uid: __expr__
- refId: C
datasourceUid: __expr__
model:
refId: C
type: threshold
expression: B
conditions:
- evaluator:
type: lt
params: [1]
datasource:
type: __expr__
uid: __expr__
How to read it:
- Step A queries Prometheus (by the data source
uidyou provisioned) for the number of healthy targets over the last ten minutes.relativeTimeRangeis in seconds. - Step B reduces the result to a single value. Expressions use the special data source UID
__expr__. - Step C is a threshold: it fires when the value is less than 1.
condition: Cmarks it as the deciding step. for: 5mrequires the condition to hold for five minutes before the alert fires, which avoids flapping.noDataStateandexecErrStatesay what to do when the query returns nothing or fails:NoData,Alerting, orOKfor the first, andError,Alerting, orOKfor the second.labelsfeed the notification policy tree, soseverity: criticalis what sent this alert down theoncall-emailroute above.uid(up to 40 characters of letters, digits,-, and_) must be unique and stable. Optionally adddashboardUidandpanelIdto link a rule to a panel.- Folder: use a folder that exists. The simplest way is to reuse the title of a folder that your dashboard provider creates.
Variable interpolation does not work in rule annotations, relativeTimeRange, the query model, or template content, so write those values out.
To delete a rule, add deleteRules with its orgId and uid:
apiVersion: 1
deleteRules:
- orgId: 1
uid: webapp-down
Apply and verify
Alerting resources are read at startup, so restart Grafana after changing the files:
docker compose restart grafana
curl -s -u admin:$GRAFANA_ADMIN_PASSWORD \
http://localhost:3000/api/v1/provisioning/alert-rules | jq '.[] | {uid, title, folderUID}'
curl -s -u admin:$GRAFANA_ADMIN_PASSWORD \
http://localhost:3000/api/v1/provisioning/contact-points | jq '.[].name'
curl -s -u admin:$GRAFANA_ADMIN_PASSWORD \
http://localhost:3000/api/v1/provisioning/policies | jq .receiver
Then test delivery by temporarily lowering a threshold, or by sending a test notification from the contact point’s API.
Writing rules faster
Rule YAML is verbose. Build a rule once in a scratch instance, then export it in provisioning format through the alerting provisioning API (the export routes under /api/v1/provisioning/, with ?format=yaml), and commit the result with a stable uid. For many similar rules, generate the YAML from a template in CI, or manage them with Terraform or the Grafana Operator, which both have alert rule resources.
Popular Guides
-
Operate Grafana as Code: The Big Picture What can live in files, what needs an API, and how the pieces fit together. -
Configure the Grafana Server with grafana.ini and Environment Variables Know where config files live, how overrides are ordered, and how to keep settings in Git. -
Provision Dashboards from JSON Files Load dashboards from disk, map folders from directories, and author them without losing changes. -
Deploy Grafana on Kubernetes with Helm and Provisioning Values Run Grafana from a Helm chart where data sources, dashboards, and settings all come from values files. -
Reload, Verify, and Troubleshoot Provisioning from the Command Line Find out what Grafana loaded, reload without a restart, and fix the usual provisioning errors.