How to get Slack notifications when StormForge applies recommendations
The StormForge Applier does its job quietly. It watches for recommendations, applies patches to your workloads, and moves on—no fanfare, no dashboard popup, nothing that reaches you unless you go looking. This works for many teams during setup and testing. But in production environments, you might want to know when your workloads change.
The Applier supports configurable webhook alerts that fire after each successful apply. You can point those alerts at any HTTP endpoint, including a Slack incoming webhook—so a Slack message lands every time the Applier applies a resource recommendation, including the affected workload, namespace, and a link to the recommendation in the StormForge UI.
This guide walks through the full setup end-to-end: creating the Slack app, designing the notification format, and configuring the Applier to deliver it. The result is a Slack notification that looks something like this:
✅ StormForge applied a recommendation
Workload: cartservice
Namespace: production
Cluster: prod-us-east-1
Patches applied: Deployment/cartservice
View recommendation
By the end, you’ll have a Slack alert that fires every time the Applier patches a workload, so changes don’t go unnoticed in the channels your team already uses.
Prerequisites
- The StormForge Agent is installed in your cluster, with the Applier running
kubectlaccess to the clusterhelmCLI (v3+)- A Slack workspace where you have permission to create apps
Part 1: Create a Slack incoming webhook
Slack incoming webhooks are a simple, token-free way to post messages to a channel. The webhook URL includes the authentication credential, which means no OAuth flow, no tokens, and no Slack API scopes to manage.
Create the Slack app
- Go to api.slack.com/apps and click Create New App.
- Choose From scratch.
- Name the app (for example,
StormForge Alerts) and select the workspace you want to post to. - Click Create App.
Enable incoming webhooks
- In the left sidebar of your app settings, click Incoming Webhooks.
- Toggle Activate Incoming Webhooks to On.
- Click Add New Webhook at the bottom of the page.
- Select the notification channel, then click Allow.
Your new webhook URL appears on the Incoming Webhooks page. Copy it—you’ll need it in Part 3.
The URL follows this pattern:
https://hooks.slack.com/services/T.../B.../XXXX
Test the webhook
Before wiring anything up in StormForge, confirm the webhook works:
curl -X POST \
-H 'Content-type: application/json' \
--data '{"text":"StormForge alert test—if you see this, the webhook is working."}' \
https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
If the webhook is configured correctly, the response is simply ok and the message appears in your channel. If you get anything else, double-check the URL.
Note: The webhook URL is the credential. Anyone with the URL can post messages to your channel. Treat it like a secret—don’t commit it to version control in plain text. See Part 3 for practical guidance on this.
Part 2: Design the notification message
The Applier sends an HTTP POST to each configured endpoint after every successful apply. The body of that request is generated from a Go template you define in your Helm values. The template produces JSON, and for Slack that JSON needs to follow Slack’s message format.
Simple text notification
The simplest Slack message uses the text field:
payloadTemplate: |
{
"text": "StormForge applied a recommendation to *{{ .workload.name }}* in namespace *{{ .workload.namespace }}*."
}
This produces a plain message with Slack’s basic Markdown formatting.
Richer notification with Block Kit
Slack’s Block Kit lets you compose structured messages with sections, context lines, and formatted links. Here’s a template that produces a well-formatted notification:
payloadTemplate: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":white_check_mark: *StormForge applied a recommendation*\n*Workload:* {{ .workload.name }}\n*Namespace:* {{ .workload.namespace }}\n*Cluster:* {{ .workload.cluster }}"
}
},
{{- if .patches }}
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Patches applied:* {{ range .patches }}{{ .patchTarget.kind }}/{{ .patchTarget.name }} {{ end }}"
}
},
{{- end }}
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "<https://app.stormforge.io/workloads/{{ .workload.cluster }}/{{ .workload.namespace }}/{{- if .patches }}{{ (index .patches 0).patchTarget.kind | lower }}s{{- end }}/{{ .workload.name }}/recommendations|View recommendation>"
}
]
}
]
}
What each block does:
- First
sectionblock: The main message body. Includes workload name, namespace, and cluster. The:white_check_mark:emoji renders as ✅ in Slack. - Second
sectionblock: Lists the applied patches. This block is included only when.patchesis non-empty, using{{- if .patches }}. contextblock: Renders the link to the recommendation in the StormForge UI using Slack’s<URL|link text>syntax format.
Available template variables:
| Variable | Value |
.recommendation | URL to the recommendation in the StormForge UI |
.workload.name | Name of the workload |
.workload.namespace | Namespace of the workload |
.workload.cluster | Cluster name |
.workload.kind | Workload kind (for example, Deployment) |
.patches | List of patches applied |
.patches[].patchTarget.name | Name of the patched resource |
.patches[].patchTarget.kind | Kind of the patched resource |
.patches[].patch | The patch content |
A toPrettyJson helper function is also available if you want to dump the full patch content as formatted JSON.
Part 3: Configure the StormForge Applier
Handle the webhook URL safely
The Slack webhook URL is sensitive. If your Helm values file is committed to version control, you don’t want it there in plain text.
A few practical approaches:
- Use a values override file that isn’t committed to source control. Keep your webhook URL in a local file (for example,
applier-secrets.yaml) and add it to.gitignore. Pass it alongside your main values file with a second-fflag when running Helm. - Use a secrets management tool like Helm Secrets, External Secrets Operator, or Vault to inject the URL at deploy time.
Create your values file
Create a file called applier-values.yaml with the alerts configuration:
alerts:
enabled: true
endpoints:
- name: slack
url: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
method: POST
contentType: application/json
payloadTemplate: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":white_check_mark: *StormForge applied a recommendation*\n*Workload:* {{ .workload.name }}\n*Namespace:* {{ .workload.namespace }}\n*Cluster:* {{ .workload.cluster }}"
}
},
{{- if .patches }}
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Patches applied:* {{ range .patches }}{{ .patchTarget.kind }}/{{ .patchTarget.name }} {{ end }}"
}
},
{{- end }}
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "<https://app.stormforge.io/workloads/{{ .workload.cluster }}/{{ .workload.namespace }}/{{- if .patches }}{{ (index .patches 0).patchTarget.kind | lower }}s{{- end }}/{{ .workload.name }}/recommendations|View recommendation>"
}
]
}
]
}
Replace https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK with the URL from Part 1.
You can configure multiple entries under endpoints: if you want to send alerts to more than one destination.
Deploy with Helm
Apply your updated values to the Applier:
helm upgrade --install stormforge-applier \
oci://registry.stormforge.io/library/stormforge-applier \
--namespace stormforge-system \
-f applier-values.yaml \
--reset-then-reuse-values
The --reset-then-reuse-values flag merges your new values with the values already deployed, so you don’t have to supply every setting in your override file.
Verify end-to-end
Trigger a recommendation apply from the StormForge UI using Apply Now, or let the Applier pick up a scheduled recommendation. Within a few seconds of the apply completing, the notification should appear in your Slack channel.
To verify on the Applier side, check the logs:
kubectl logs -n stormforge-system \
-l component=applier \
--tail=50
Look for a log entry containing alerting endpoints of recommendation applied. That line confirms the Applier detected the successful apply and attempted to notify your endpoints.
Troubleshooting
No message appears in Slack:
Check the Applier logs for errors after the alerting endpoints log line. Then verify the webhook URL is correct by pasting it into the curl test command from Part 1. If you get anything other than ok, the URL is wrong.
JSON parse error in the Applier logs:
The payloadTemplate is producing malformed JSON. Look for extra commas, missing braces, or whitespace issues around conditional blocks.
Recommendation applied but no alert fired:
Confirm that alerts.enabled: true is actually deployed. Run:
helm get values stormforge-applier -n stormforge-system
Check the output for the alerts block. If it’s missing or enabled is false, your values file didn’t get applied correctly.
Go template syntax error:
Check for balanced {{ and }}, and verify variable names match the list in Part 2.
What’s next
- The StormForge docs include a guide covering additional alert integration patterns, including routing alerts to Amazon SNS for teams who want notifications to flow into existing event infrastructure.
- Use the
.patchesvariable to build more detailed notifications that list every container whose CPU and memory limits changed.
Related Blogs
What Teams Actually Need Before They’ll Let Right-Sizing Act in ProductionÂ
Most Kubernetes teams know they’re overprovisioned. The dashboards show it. The recommendations confirm it. And in most environments, the list of workloads…