Skip to main content

Argo CD to Slack Notifications: The Complete Setup Guide

This document outlines the step-by-step process to configure Argo CD to send rich, detailed notifications to Slack whenever an application syncs, fails, or degrades.

βœ… Prerequisites

  • Argo CD installed on your Kubernetes cluster.
  • Admin access to a Slack Workspace.
  • kubectl access to your cluster.

1. Configure Slack App & Bot Token

We need to create a β€œBot” user that Argo CD can use to post messages.
  1. Go to Slack API: Your Apps.
  2. Click Create New App > From Scratch.
  3. Name it β€œArgoCD” and select your workspace.
  4. In the left sidebar, go to OAuth & Permissions.
  5. Scroll down to Scopes > Bot Token Scopes and add the following permission:
  • chat:write (Required to send messages)
  • chat:write.customize (Optional: Allows changing username/icon dynamically)
  1. Scroll up to the top of the page and click Install to Workspace.
  2. Copy the Bot User OAuth Token (starts with xoxb-...).

⚠️ Important: Invite the Bot

For the bot to post in a channel, it must be a member of that channel.
  • Go to your target Slack channel (e.g., #jenkins-ntfy).
  • Type /invite @ArgoCD and hit Enter.

2. Create the Kubernetes Secret (Safely)

We must store the Slack token in a Kubernetes Secret. CRITICAL: We use echo -n to prevent hidden newline characters (\n) from breaking the configuration. Run this command in your terminal (replace xoxb-YOUR-TOKEN with your actual token):
kubectl create secret generic argocd-notifications-secret \
  -n argocd \
  --from-literal=slack-token=$(echo -n "xoxb-YOUR-REAL-TOKEN-HERE") \
  --dry-run=client -o yaml | kubectl apply -f -


3. Configure Argo CD Notifications

We will configure the argocd-notifications-cm ConfigMap. This defines Service, Triggers, Subscriptions, and the Message Templates.

Features of this Configuration:

  • Rich Formatting: Uses Slack Attachments for a professional β€œCloudWatch” look.
  • Detailed Info: Lists Repo URL, Commit SHA, and Sync Status.
  • Resource List: loops through changed objects (e.g., Service my-svc -> configured) so developers see exactly what changed.
  • Deep Links: Clickable titles take you directly to the Argo CD dashboard.

πŸ“„ argocd-notifications-cm.yaml

Save the following content to a file named argocd-notifications-cm.yaml. **Update the argocdUrl** at the top with your actual Argo CD domain.
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  # ---------------------------------------------------------------------------
  # 1. CONTEXT (Replace with your actual Argo CD URL for clickable links)
  # ---------------------------------------------------------------------------
  context: |
    argocdUrl: https://argocd.your-domain.com

  # ---------------------------------------------------------------------------
  # 2. SLACK SERVICE CONFIGURATION
  # ---------------------------------------------------------------------------
  service.slack: |
    token: $slack-token
    username: ArgoCD
    # icon: :rocket:  <-- Do not use this line, it causes YAML parsing errors.

  # ---------------------------------------------------------------------------
  # 3. GLOBAL SUBSCRIPTION (Applies to ALL Applications)
  # ---------------------------------------------------------------------------
  subscriptions: |
    - recipients:
      - slack:jenkins-ntfy   # <--- Change to your Slack Channel name
      triggers:
      - on-sync-succeeded
      - on-sync-failed
      - on-health-degraded
      - on-deployed

  # ---------------------------------------------------------------------------
  # 4. TRIGGERS (Logic for when to fire alerts)
  # ---------------------------------------------------------------------------
  trigger.on-sync-succeeded: |
    - description: Application syncing has succeeded
      send: [app-sync-succeeded]
      when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'

  trigger.on-sync-failed: |
    - description: Application syncing has failed
      send: [app-sync-failed]
      when: app.status.operationState.phase in ['Error', 'Failed']

  trigger.on-health-degraded: |
    - description: Application has degraded
      send: [app-health-degraded]
      when: app.status.health.status == 'Degraded'

  trigger.on-deployed: |
    - description: Application is synced and healthy. Triggered once per commit.
      oncePer: app.status.operationState.syncResult.revision
      send: [app-deployed]
      when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'

  # ---------------------------------------------------------------------------
  # 5. TEMPLATES (The "CloudWatch-Style" Message Design)
  # ---------------------------------------------------------------------------

  # βœ… SYNC SUCCEEDED
  template.app-sync-succeeded: |
    message: |
      βœ… Application {{.app.metadata.name}} Synced.
    slack:
      attachments: |
        [{
          "color": "#18be52",
          "title": "βœ… Sync Succeeded: {{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "fields": [
            {
              "title": "Application",
              "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
              "short": true
            },
            {
              "title": "Repo URL",
              "value": "{{.app.spec.source.repoURL}}",
              "short": true
            },
            {
              "title": "Commit",
              "value": "`{{.app.status.operationState.syncResult.revision}}`",
              "short": true
            },
            {
              "title": "Status",
              "value": "Sync: {{.app.status.operationState.phase}}\nHealth: {{.app.status.health.status}}",
              "short": true
            },
            {
              "title": "πŸ“¦ Changed Objects",
              "value": "```{{range .app.status.operationState.syncResult.resources}}β€’ {{.kind}} {{.name}}: {{.message}}\n{{end}}```",
              "short": false
            }
          ],
          "footer": "Argo CD"
        }]

  # ❌ SYNC FAILED
  template.app-sync-failed: |
    message: |
      ❌ Application {{.app.metadata.name}} Failed to Sync.
    slack:
      attachments: |
        [{
          "color": "#ff0000",
          "title": "❌ Sync Failed: {{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "fields": [
            {
              "title": "Application",
              "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
              "short": true
            },
            {
              "title": "Error Message",
              "value": "```{{.app.status.operationState.message}}```",
              "short": false
            },
            {
              "title": "Commit",
              "value": "`{{.app.status.operationState.syncResult.revision}}`",
              "short": true
            }
          ],
          "footer": "Argo CD"
        }]

  # ⚠️ HEALTH DEGRADED
  template.app-health-degraded: |
    message: |
      ⚠️ Application {{.app.metadata.name}} is Degraded.
    slack:
      attachments: |
        [{
          "color": "#ffca00",
          "title": "⚠️ Health Degraded: {{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "fields": [
            {
              "title": "Application",
              "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
              "short": true
            },
            {
              "title": "Health Status",
              "value": "{{.app.status.health.status}}",
              "short": true
            }
          ],
          "footer": "Argo CD"
        }]

  # πŸš€ DEPLOYED (Triggered on new commit syncs)
  template.app-deployed: |
    message: |
      πŸš€ New Version Deployed: {{.app.metadata.name}}
    slack:
      attachments: |
        [{
          "color": "#2eb886",
          "title": "πŸš€ Deployed: {{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "fields": [
            {
              "title": "Application",
              "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
              "short": true
            },
            {
              "title": "New Commit",
              "value": "`{{.app.status.operationState.syncResult.revision}}`",
              "short": true
            },
            {
              "title": "πŸ“¦ Affected Objects",
              "value": "```{{range .app.status.operationState.syncResult.resources}}β€’ {{.kind}} {{.name}} -> {{.message}}\n{{end}}```",
              "short": false
            }
          ],
          "footer": "Argo CD"
        }]


4. Apply and Restart

Apply the configuration and restart the controller to force it to reload the settings.
# 1. Apply the ConfigMap
kubectl apply -f argocd-notifications-cm.yaml

# 2. Restart the Controller
kubectl rollout restart deployment argocd-notifications-controller -n argocd

# Note: If using Argo CD v2.3+ bundled controller, restart the application-controller instead:
# kubectl rollout restart deployment argocd-application-controller -n argocd


5. Verification

  1. Check the logs to ensure no YAML errors:
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-notifications-controller --tail=20 -f

  1. Go to the Argo CD UI.
  2. Click Sync on any application.
  3. Check your Slack channel. You should see a detailed card listing the sync status and the specific objects (Pods/Services) that were updated.

πŸ”§ Troubleshooting Guide (Common Issues)

IssueCauseFix
Error: yaml: line 2: mapping values are not allowedHidden Newline in Secret. The Slack token has a \n at the end.Delete secret and recreate using echo -n "token".
Error: template: unexpected ".0" in operandInvalid Go Template Syntax. Accessing arrays like .history.0 is not allowed.Use index .history 0 or remove the field (as we did in the final config).
Slack: <<no value>/applications/...>Missing Context URL. The template cannot generate links because it doesn’t know your domain.Add `context:argocdUrl: https://…` to the ConfigMap.
Boot Loop / CrashYAML Formatting. Using colons inside values without quotes (e.g., icon: :rocket:).Remove the icon line or quote it properly "icon": ":rocket:".


ArgoSecret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: argocd-notifications-secret
  namespace: argocd
stringData:
  slack-token: xoxb-xxxxxxxxxxxxxxxxxxxx
type: Opaque
ArgoCM.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  # ---------------------------------------------------------------------------
  # 1. CONTEXT (Fixes the <no value> link issue)
  # ---------------------------------------------------------------------------
  context: |
    argocdUrl: http://argo-uat.internal.domain.co.in

  # ---------------------------------------------------------------------------
  # 2. SLACK TOKEN & SETTINGS
  # ---------------------------------------------------------------------------
  service.slack: |
    token: xoxb-xxxxxxxxxxxxxxxxxxxx
    username: ArgoCD

  # ---------------------------------------------------------------------------
  # 3. SUBSCRIPTIONS
  # ---------------------------------------------------------------------------
  subscriptions: |
    - recipients:
      - slack:jenkins-ntfy
      triggers:
      - on-sync-succeeded
      - on-sync-failed
      - on-health-degraded
      - on-deployed

  # ---------------------------------------------------------------------------
  # 4. TRIGGERS
  # ---------------------------------------------------------------------------
  trigger.on-sync-succeeded: |
    - description: Application syncing has succeeded
      send: [app-sync-succeeded]
      when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'

  trigger.on-sync-failed: |
    - description: Application syncing has failed
      send: [app-sync-failed]
      when: app.status.operationState.phase in ['Error', 'Failed']

  trigger.on-health-degraded: |
    - description: Application has degraded
      send: [app-health-degraded]
      when: app.status.health.status == 'Degraded'

  # trigger.on-deployed: |
  #   - description: Application is synced and healthy. Triggered once per commit.
  #     oncePer: app.status.operationState.syncResult.revision
  #     send: [app-deployed]
  #     when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'

  # ---------------------------------------------------------------------------
  # 5. TEMPLATES (With Action Details: Configured/Created)
  # ---------------------------------------------------------------------------

  # βœ… SYNC SUCCEEDED
  template.app-sync-succeeded: |
    message: |
      πŸš€ Application {{.app.metadata.name}} Synced.
    slack:
      attachments: |
        [{
          "color": "#18be52",
          "title": "βœ… Sync Succeeded: {{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "fields": [
            {
              "title": "Application",
              "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
              "short": true
            },
            {
              "title": "Repo URL",
              "value": "{{.app.spec.source.repoURL}}",
              "short": true
            },
            {
              "title": "Commit",
              "value": "`{{.app.status.operationState.syncResult.revision}}`",
              "short": true
            },
            {
              "title": "Status",
              "value": "Sync: {{.app.status.operationState.phase}}\nHealth: {{.app.status.health.status}}",
              "short": true
            },
            {
              "title": "πŸ“¦ Changed Objects",
              "value": "```{{range .app.status.operationState.syncResult.resources}}β€’ {{.kind}} {{.name}}: {{.message}}\n{{end}}```",
              "short": false
            }
          ],
          "footer": "Argo CD"
        }]

  # ❌ SYNC FAILED
  template.app-sync-failed: |
    message: |
      ❌ Application {{.app.metadata.name}} Failed to Sync.
    slack:
      attachments: |
        [{
          "color": "#ff0000",
          "title": "❌ Sync Failed: {{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "fields": [
            {
              "title": "Application",
              "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
              "short": true
            },
            {
              "title": "Error Message",
              "value": "```{{.app.status.operationState.message}}```",
              "short": false
            },
            {
              "title": "Commit",
              "value": "`{{.app.status.operationState.syncResult.revision}}`",
              "short": true
            }
          ],
          "footer": "Argo CD"
        }]

  # ⚠️ HEALTH DEGRADED
  template.app-health-degraded: |
    message: |
      ⚠️ Application {{.app.metadata.name}} is Degraded.
    slack:
      attachments: |
        [{
          "color": "#ffca00",
          "title": "⚠️ Health Degraded: {{.app.metadata.name}}",
          "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "fields": [
            {
              "title": "Application",
              "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
              "short": true
            },
            {
              "title": "Health Status",
              "value": "{{.app.status.health.status}}",
              "short": true
            }
          ],
          "footer": "Argo CD"
        }]

  # # πŸš€ DEPLOYED
  # template.app-deployed: |
  #   message: |
  #     πŸš€ New Version Deployed: {{.app.metadata.name}}
  #   slack:
  #     attachments: |
  #       [{
  #         "color": "#2eb886",
  #         "title": "πŸš€ Deployed: {{.app.metadata.name}}",
  #         "title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
  #         "fields": [
  #           {
  #             "title": "Application",
  #             "value": "<{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|{{.app.metadata.name}}>",
  #             "short": true
  #           },
  #           {
  #             "title": "New Commit",
  #             "value": "`{{.app.status.operationState.syncResult.revision}}`",
  #             "short": true
  #           },
  #           {
  #             "title": "πŸ“¦ Affected Objects",
  #             "value": "```{{range .app.status.operationState.syncResult.resources}}β€’ {{.kind}} {{.name}} -> {{.message}}\n{{end}}```",
  #             "short": false
  #           }
  #         ],
  #         "footer": "Argo CD"
  #       }]

Restart Services
kubectl rollout restart deployment argocd-notifications-controller -n argocd