A lightweight, stateless HTTP bridge that receives alerts from Alertmanager, transforms them through Go templates, and executes user-defined commands with proper timeout, logging, and HTTP success/error semantics.
Alertmanager ships with excellent integrations (email, Slack, PagerDuty, webhooks), but many real-world environments require executing arbitrary commands when an alert fires. For example:
- Triggering on-prem automations
- Sending messages through a custom API
- Running scripts or operational tooling
- Integrating with systems that do not expose webhook endpoints
This project fills that gap by providing a small, safe, configurable, Go-based HTTP service that accepts Alertmanager webhooks and turns them into controllable command executions.
The philosophy is:
- Define exactly which command to run.
- Use Go templates to render arguments from the incoming alert.
- Enforce execution timeout.
- Emit structured JSON logs.
- Return success or failure to Alertmanager via HTTP.
alertmanager-exec-bridge is designed to run in the same Pod as Alertmanager. Alertmanager calls it via:
url: http://127.0.0.1:9095/alert
Because it is stateless and configuration-driven, the container can be restarted at any time without losing context.
- No external network dependencies
- No need to expose a public endpoint
- Low latency between Alertmanager and the bridge
- Isolated responsibility: Alertmanager routes alerts, bridge executes logic
The service does not persist:
- alert state
- execution history
- configuration
All configuration is provided via command-line flags, environment variables, or a mounted config file.
--
Deploying this bridge as an Alertmanager sidecar introduces an additional hop in the alert delivery pipeline, but it does not have to introduce a new silent single point of failure.
The failure model should be:
- If the sidecar container crashes or fails its liveness/readiness checks, the entire Alertmanager Pod becomes NotReady and is removed from the Service endpoints.
- Operationally, this instance is treated exactly as “Alertmanager down” and must be covered by your existing fallback / secondary alerting channel (e.g. email, PagerDuty, another Alertmanager route).
In other words, the bridge becomes part of the health contract of the Alertmanager Pod: if the bridge is not healthy, this instance is not considered a valid backend.
There are still two important failure modes you must monitor explicitly:
- The bridge process is running, but returns
5xxto/alert(delivery failures to the external system). - Alertmanager is healthy, but the external receiver (e.g. Evolution API) is unavailable.
Both conditions should be covered by additional alerts (e.g. based on Alertmanager metrics about failed notifications, or logs from the bridge), and by having at least one independent notification channel for “Alertmanager / alert pipeline is broken”.
go run ./cmd/alert-exec --config configs/default.yaml
curl -X POST http://localhost:9095/alert \
-H 'Content-Type: application/json' \
-d '{
"version": "4",
"status": "firing",
"receiver": "test",
"groupKey": "{}:{}",
"alerts": [
{
"status": "firing",
"labels": {"alertname": "TestAlert"},
"annotations": {"summary": "Demo"},
"startsAt": "2025-01-01T00:00:00Z"
}
]
}'
{"level":"INFO","msg":"alert mapped to command", ...}
{"level":"INFO","msg":"command execution succeeded", ...}
Configuration values come from three sources (priority descending):
- Command-line arguments
- Environment variables (
ALERT_EXEC_*) - YAML config file
Address to bind the HTTP server.
listen: ":9095"
Shared secret expected in the X-Token header.
Empty value disables authentication.
token: "my-secret"
Path to the executable or script. Can be templated.
command: "/bin/echo"
Arguments to pass to the executable. Each entry supports Go templates.
args:
- '{{ .Status }}'
- '{{ .PrimaryAlertName }}'
Execution timeout for the command.
timeout: "5s"
Valid values: debug, info, warn, error.
log_level: "info"
Health probe. Always returns 200 OK.
Consumes the Alertmanager webhook format. Returns:
200 OKif the command executed successfully503 Service Unavailableif execution failed or timed out
Body includes structured JSON describing the outcome.
The official container image is published automatically to GitHub Container Registry (GHCR).
ghcr.io/${{ github.repository }}:latest
Replace ${{ github.repository }} with your actual repository path, e.g.:
ghcr.io/jdanieu/alertmanager-exec-bridge:latest
All published tags are visible here:
https://ghcr.io/jdanieu/alertmanager-exec-bridge
(or directly in the Packages section of the GitHub repository)
Basic usage:
docker run --rm -p 9095:9095 ghcr.io/jdanieu/alertmanager-exec-bridge:latest \
--listen :9095 \
--command /bin/echo
Run with a configuration file mounted:
docker run --rm -p 9095:9095 \
-v $(pwd)/configs/default.yaml:/config.yaml \
ghcr.io/jdanieu/alertmanager-exec-bridge:latest \
--config /config.yaml
You can now POST alerts exactly as in local development.
- Validates incoming JSON using typed structs
- Renders
command+argsusing Go templates - Executes the command synchronously
- Enforces timeout via
executor.Run - Captures stdout, stderr, exit code, and duration
- Logs everything in JSON via
slog - Returns success/failure to Alertmanager with appropriate HTTP codes
containers:
- name: alert-exec
image: jdanieu/alert-exec:latest
args:
- "--config=/config/config.yaml"
ports:
- containerPort: 9095
volumeMounts:
- name: alert-exec-config
mountPath: /config
volumes:
- name: alert-exec-config
configMap:
name: alert-exec-configAlertmanager configuration:
webhook_configs:
- url: "http://127.0.0.1:9095/alert"
http_config:
headers:
X-Token: "my-secret"Issues and pull requests are welcome. Keep the code minimal, clear, and production-safe.