All resource types

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
  • kubectl access to the cluster
  • helm CLI (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

  1. Go to api.slack.com/apps and click Create New App.
  2. Choose From scratch.
  3. Name the app (for example, StormForge Alerts) and select the workspace you want to post to.
  4. Click Create App.

Enable incoming webhooks

  1. In the left sidebar of your app settings, click Incoming Webhooks.
  2. Toggle Activate Incoming Webhooks to On.
  3. Click Add New Webhook at the bottom of the page.
  4. 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 section block: The main message body. Includes workload name, namespace, and cluster. The :white_check_mark: emoji renders as ✅ in Slack.
  • Second section block: Lists the applied patches. This block is included only when .patches is non-empty, using {{- if .patches }}.
  • context block: Renders the link to the recommendation in the StormForge UI using Slack’s <URL|link text> syntax format.

Available template variables:

VariableValue
.recommendationURL to the recommendation in the StormForge UI
.workload.nameName of the workload
.workload.namespaceNamespace of the workload
.workload.clusterCluster name
.workload.kindWorkload kind (for example, Deployment)
.patchesList of patches applied
.patches[].patchTarget.nameName of the patched resource
.patches[].patchTarget.kindKind of the patched resource
.patches[].patchThe 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 -f flag 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 .patches variable to build more detailed notifications that list every container whose CPU and memory limits changed.

Sign up for our newsletter

Exclusive insights and strategies for cloud pros. Delivered straight to your inbox.


AUTHOR
Rachel Rice

Senior Technical Writer, CloudBolt

Rachel Rice is a Senior Technical Writer at CloudBolt with a background in cloud security, QA, and development. She has a keen eye for detail and a sharp instinct for what engineers actually need...
  Learn more

Related Blogs

 
thumbnail
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…

 
thumbnail
The VMware Shakeup Hits Europe Differently: Sovereignty Isn’t a Preference, It’s a Constraint 

If you’re watching the hypervisor market shift from Europe, the conversation sounds different from what it does in North America.  Not because…

 
thumbnail
Why Cloud Resource Optimization Is Moving Beyond Recommendations

Cloud resource optimization has typically followed this pattern: teams identify inefficiencies, generate recommendations, review them, and apply changes where it feels safe to…