Set up ArgoCD notification controller to send application deployment alerts and sync status updates to Slack channels and Microsoft Teams. Configure webhook integrations with custom templates and triggers for production GitOps workflows.
Prerequisites
- Existing Kubernetes cluster with ArgoCD installed
- Slack workspace admin access
- Microsoft Teams admin access
- kubectl configured
What this solves
ArgoCD notifications keep your team informed about application deployments, sync failures, and health status changes in your GitOps workflows. This tutorial configures the ArgoCD notification controller to send alerts to Slack and Microsoft Teams using webhooks, enabling real-time visibility into your Kubernetes deployments.
Step-by-step configuration
Install ArgoCD notification controller
The notification controller is a separate component that handles all notification logic for ArgoCD. Install it in your ArgoCD namespace.
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/notifications_install.yaml
Verify notification controller deployment
Check that the notification controller pod is running and ready to process notifications.
kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-notifications-controller
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-notifications-controller
Create Slack webhook URL
Generate a webhook URL in your Slack workspace to receive ArgoCD notifications. Go to your Slack admin panel, create a new app, and enable incoming webhooks.
Create Microsoft Teams webhook URL
Generate a webhook connector in Microsoft Teams. Go to your Teams channel, click "Connectors", search for "Incoming Webhook", and create a new connector.
Configure notification secrets
Store your webhook URLs as Kubernetes secrets so ArgoCD can authenticate with Slack and Teams.
kubectl create secret generic argocd-notifications-secret \
--from-literal=slack-token="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" \
--from-literal=teams-webhook="https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK" \
-n argocd
Create notification configuration
Define notification services, templates, and triggers in the ArgoCD notifications ConfigMap.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
username: ArgoCD
channel: "#deployments"
iconEmoji: ":rocket:"
service.teams: |
recipientUrls:
general: $teams-webhook
template.app-deployed: |
webhook:
teams:
method: POST
body: |
{
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": "Application {{.app.metadata.name}} deployed",
"themeColor": "00FF00",
"sections": [{
"activityTitle": "Application Deployed Successfully",
"activitySubtitle": "{{.app.metadata.name}} in {{.app.spec.destination.namespace}}",
"facts": [
{"name": "Repository", "value": "{{.app.spec.source.repoURL}}"},
{"name": "Revision", "value": "{{.app.status.sync.revision}}"},
{"name": "Sync Status", "value": "{{.app.status.sync.status}}"}
]
}]
}
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [
{"title": "Sync Status", "value": "{{.app.status.sync.status}}", "short": true},
{"title": "Repository", "value": "{{.app.spec.source.repoURL}}", "short": true}
]
}]
template.app-sync-failed: |
webhook:
teams:
method: POST
body: |
{
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": "Application {{.app.metadata.name}} sync failed",
"themeColor": "FF0000",
"sections": [{
"activityTitle": "Application Sync Failed",
"activitySubtitle": "{{.app.metadata.name}} in {{.app.spec.destination.namespace}}",
"facts": [
{"name": "Repository", "value": "{{.app.spec.source.repoURL}}"},
{"name": "Sync Status", "value": "{{.app.status.sync.status}}"},
{"name": "Message", "value": "{{.app.status.conditions[0].message}}"}
]
}]
}
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#E96D76",
"fields": [
{"title": "Sync Status", "value": "{{.app.status.sync.status}}", "short": true},
{"title": "Repository", "value": "{{.app.spec.source.repoURL}}", "short": true}
]
}]
trigger.on-deployed: |
- description: Application is synced and healthy
send:
- app-deployed
when: app.status.sync.status == 'Synced' and app.status.health.status == 'Healthy'
trigger.on-sync-failed: |
- description: Application sync failed
send:
- app-sync-failed
when: app.status.sync.status == 'OutOfSync'
subscriptions: |
- recipients:
- slack:deployments
- teams:general
triggers:
- on-deployed
- on-sync-failed
Apply notification configuration
Deploy the notification configuration to your ArgoCD namespace.
kubectl apply -f argocd-notifications-cm.yaml
Enable notifications for specific applications
Add notification annotations to your ArgoCD applications to enable alerts. You can do this via the UI or by updating application manifests.
kubectl patch app your-app-name -n argocd --type merge -p='{
"metadata": {
"annotations": {
"notifications.argoproj.io/subscribe.on-deployed.slack": "deployments",
"notifications.argoproj.io/subscribe.on-sync-failed.teams": "general"
}
}
}'
Configure global notification subscriptions
Set up default subscriptions for all applications by updating the notifications ConfigMap with global settings.
kubectl patch configmap argocd-notifications-cm -n argocd --type merge -p='{
"data": {
"subscriptions": "- recipients:\n - slack:deployments\n - teams:general\n triggers:\n - on-deployed\n - on-sync-failed"
}
}'
Create custom notification templates
Add additional templates for different notification scenarios like health status changes and sync operations.
kubectl patch configmap argocd-notifications-cm -n argocd --type merge -p='{
"data": {
"template.app-health-degraded": "webhook:\n slack:\n attachments: |\n [{\n \"title\": \"{{.app.metadata.name}}\",\n \"title_link\": \"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}\",\n \"color\": \"#f4c030\",\n \"fields\": [\n {\"title\": \"Health Status\", \"value\": \"{{.app.status.health.status}}\", \"short\": true},\n {\"title\": \"Repository\", \"value\": \"{{.app.spec.source.repoURL}}\", \"short\": true}\n ]\n }]\n teams:\n method: POST\n body: |\n {\n \"@type\": \"MessageCard\",\n \"@context\": \"https://schema.org/extensions\",\n \"summary\": \"Application {{.app.metadata.name}} health degraded\",\n \"themeColor\": \"FFA500\",\n \"sections\": [{\n \"activityTitle\": \"Application Health Degraded\",\n \"activitySubtitle\": \"{{.app.metadata.name}} in {{.app.spec.destination.namespace}}\",\n \"facts\": [\n {\"name\": \"Health Status\", \"value\": \"{{.app.status.health.status}}\"},\n {\"name\": \"Repository\", \"value\": \"{{.app.spec.source.repoURL}}\"}\n ]\n }]\n }",
"trigger.on-health-degraded": "- description: Application health degraded\n send:\n - app-health-degraded\n when: app.status.health.status == 'Degraded'"
}
}'
Restart notification controller
Restart the notification controller to load the new configuration and templates.
kubectl rollout restart deployment argocd-notifications-controller -n argocd
kubectl rollout status deployment argocd-notifications-controller -n argocd
Configure advanced notification features
Set up notification timing controls
Configure notification frequency and timing to prevent spam and ensure relevant alerts only.
kubectl patch configmap argocd-notifications-cm -n argocd --type merge -p='{
"data": {
"trigger.on-sync-status-unknown": "- description: Application sync status unknown for 10 minutes\n send:\n - app-sync-failed\n when: app.status.sync.status == 'Unknown'\n oncePer: app.status.sync.revision"
}
}'
Configure notification filtering
Set up filters to send notifications only for specific namespaces, projects, or application labels.
kubectl patch configmap argocd-notifications-cm -n argocd --type merge -p='{
"data": {
"trigger.on-deployed-production": "- description: Production application deployed\n send:\n - app-deployed\n when: app.status.sync.status == 'Synced' and app.status.health.status == 'Healthy' and app.spec.destination.namespace == 'production'"
}
}'
Verify your setup
kubectl get configmap argocd-notifications-cm -n argocd -o yaml
kubectl get secret argocd-notifications-secret -n argocd
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-notifications-controller
kubectl get apps -n argocd -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations}{"\n"}{end}'
Test notifications by triggering a sync operation or changing application health status. You can also use the ArgoCD CLI to send test notifications.
argocd admin notifications trigger on-deployed --app-name your-app-name --recipient slack:deployments
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Notifications not sent | Wrong webhook URL format | Verify webhook URLs in secret and test manually with curl |
| Controller pod crashloop | Invalid YAML in ConfigMap | Check ConfigMap syntax with kubectl apply --dry-run=client |
| Teams notifications malformed | Invalid JSON in template | Validate JSON template with online validator |
| Slack attachments not showing | Missing webhook permissions | Recreate Slack app with correct scopes |
| Triggers not firing | Wrong condition syntax | Check trigger conditions match actual app status |
Next steps
- Install and configure ArgoCD for GitOps continuous deployment with RBAC and SSL
- Implement Kubernetes network policies with Calico CNI and OPA Gatekeeper for security enforcement
- Configure ArgoCD Image Updater for automated container deployments
- Setup ArgoCD ApplicationSets for multi-environment GitOps workflows
- Integrate ArgoCD with External Secret Operator for Kubernetes secret management
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# ArgoCD Notifications Configuration Script for Slack and Microsoft Teams
# Supports: Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS, RHEL, Amazon Linux
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
ARGOCD_NAMESPACE="${ARGOCD_NAMESPACE:-argocd}"
SLACK_WEBHOOK="${1:-}"
TEAMS_WEBHOOK="${2:-}"
# Usage message
usage() {
echo "Usage: $0 <slack_webhook_url> <teams_webhook_url> [argocd_namespace]"
echo "Example: $0 'https://hooks.slack.com/services/T00/B00/XXX' 'https://outlook.office.com/webhook/...' argocd"
exit 1
}
# Check arguments
if [[ -z "$SLACK_WEBHOOK" || -z "$TEAMS_WEBHOOK" ]]; then
echo -e "${RED}Error: Missing required webhook URLs${NC}"
usage
fi
if [[ $# -ge 3 ]]; then
ARGOCD_NAMESPACE="$3"
fi
# Cleanup function
cleanup() {
echo -e "${YELLOW}Cleaning up temporary files...${NC}"
rm -f /tmp/argocd-notifications-*.yaml 2>/dev/null || true
}
trap cleanup ERR EXIT
# Detect OS and set package manager
echo -e "${YELLOW}[1/8] Detecting operating system...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect OS. /etc/os-release not found.${NC}"
exit 1
fi
echo -e "${GREEN}Detected: $PRETTY_NAME using $PKG_MGR${NC}"
# Check if running as root or with sudo
echo -e "${YELLOW}[2/8] Checking privileges...${NC}"
if [[ $EUID -ne 0 && -z "${SUDO_USER:-}" ]]; then
echo -e "${RED}This script must be run as root or with sudo${NC}"
exit 1
fi
# Install required packages
echo -e "${YELLOW}[3/8] Installing required packages...${NC}"
$PKG_UPDATE
if command -v kubectl >/dev/null 2>&1; then
echo -e "${GREEN}kubectl already installed${NC}"
else
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL curl apt-transport-https ca-certificates gnupg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' > /etc/apt/sources.list.d/kubernetes.list
apt update
$PKG_INSTALL kubectl
else
cat > /etc/yum.repos.d/kubernetes.repo << 'EOF'
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key
EOF
$PKG_INSTALL kubectl
fi
fi
# Verify kubectl connectivity
echo -e "${YELLOW}[4/8] Verifying Kubernetes connectivity...${NC}"
if ! kubectl cluster-info >/dev/null 2>&1; then
echo -e "${RED}Cannot connect to Kubernetes cluster. Please configure kubectl.${NC}"
exit 1
fi
# Check if ArgoCD namespace exists
echo -e "${YELLOW}[5/8] Checking ArgoCD namespace...${NC}"
if ! kubectl get namespace "$ARGOCD_NAMESPACE" >/dev/null 2>&1; then
echo -e "${RED}ArgoCD namespace '$ARGOCD_NAMESPACE' not found. Please install ArgoCD first.${NC}"
exit 1
fi
# Install ArgoCD notifications controller
echo -e "${YELLOW}[6/8] Installing ArgoCD notifications controller...${NC}"
kubectl apply -n "$ARGOCD_NAMESPACE" -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/notifications_install.yaml
# Wait for controller to be ready
echo "Waiting for notifications controller to be ready..."
kubectl wait --for=condition=available deployment/argocd-notifications-controller -n "$ARGOCD_NAMESPACE" --timeout=300s
# Create secrets for webhooks
echo -e "${YELLOW}[7/8] Creating notification secrets...${NC}"
kubectl create secret generic argocd-notifications-secret \
--from-literal=slack-token="$SLACK_WEBHOOK" \
--from-literal=teams-webhook="$TEAMS_WEBHOOK" \
-n "$ARGOCD_NAMESPACE" \
--dry-run=client -o yaml | kubectl apply -f -
# Create notifications configuration
echo "Creating notification configuration..."
cat > /tmp/argocd-notifications-cm.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: NAMESPACE_PLACEHOLDER
data:
service.slack: |
token: $slack-token
username: ArgoCD
channel: "#deployments"
iconEmoji: ":rocket:"
service.teams: |
recipientUrls:
general: $teams-webhook
template.app-deployed: |
webhook:
teams:
method: POST
body: |
{
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": "Application {{.app.metadata.name}} deployed",
"themeColor": "00FF00",
"sections": [{
"activityTitle": "Application Deployed Successfully",
"activitySubtitle": "{{.app.metadata.name}} in {{.app.spec.destination.namespace}}",
"facts": [
{"name": "Repository", "value": "{{.app.spec.source.repoURL}}"},
{"name": "Revision", "value": "{{.app.status.sync.revision}}"},
{"name": "Sync Status", "value": "{{.app.status.sync.status}}"}
]
}]
}
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [
{"title": "Sync Status", "value": "{{.app.status.sync.status}}", "short": true},
{"title": "Repository", "value": "{{.app.spec.source.repoURL}}", "short": true}
]
}]
template.app-sync-failed: |
webhook:
teams:
method: POST
body: |
{
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": "Application {{.app.metadata.name}} sync failed",
"themeColor": "FF0000",
"sections": [{
"activityTitle": "Application Sync Failed",
"activitySubtitle": "{{.app.metadata.name}} in {{.app.spec.destination.namespace}}",
"facts": [
{"name": "Repository", "value": "{{.app.spec.source.repoURL}}"},
{"name": "Sync Status", "value": "{{.app.status.sync.status}}"}
]
}]
}
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#E96D76",
"fields": [
{"title": "Sync Status", "value": "{{.app.status.sync.status}}", "short": true},
{"title": "Repository", "value": "{{.app.spec.source.repoURL}}", "short": true}
]
}]
trigger.on-deployed: |
- description: Application is synced and healthy
oncePer: app.status.sync.revision
send:
- app-deployed
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']
EOF
# Replace namespace placeholder and apply
sed "s/NAMESPACE_PLACEHOLDER/$ARGOCD_NAMESPACE/g" /tmp/argocd-notifications-cm.yaml | kubectl apply -f -
# Verify installation
echo -e "${YELLOW}[8/8] Verifying installation...${NC}"
echo "Checking notifications controller status..."
kubectl get pods -n "$ARGOCD_NAMESPACE" -l app.kubernetes.io/name=argocd-notifications-controller
echo "Checking configuration..."
kubectl get configmap argocd-notifications-cm -n "$ARGOCD_NAMESPACE" >/dev/null
kubectl get secret argocd-notifications-secret -n "$ARGOCD_NAMESPACE" >/dev/null
echo -e "${GREEN}✓ ArgoCD notifications controller installed successfully${NC}"
echo -e "${GREEN}✓ Slack and Teams webhooks configured${NC}"
echo -e "${GREEN}✓ Notification templates and triggers created${NC}"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Add notifications to your ArgoCD applications with annotations:"
echo " notifications.argoproj.io/subscribe.on-deployed.slack: \"\""
echo " notifications.argoproj.io/subscribe.on-sync-failed.teams: \"\""
echo "2. Monitor logs: kubectl logs -n $ARGOCD_NAMESPACE -l app.kubernetes.io/name=argocd-notifications-controller"
Review the script before running. Execute with: bash install.sh