The CyberController Container Watchdog is a lightweight, autonomous monitoring service that continuously tracks the health of all Docker containers on the host. It detects failures — including crashes, out-of-memory kills, prolonged unhealthy states, and restart loops — and dispatches real-time alerts through one or more configurable channels (Slack, SMTP, SNMP Traps, or Syslog). It also sends a one-time INFO recovered alert through the same channels once a previously-alarmed container returns to normal operation.
- How It Works
- System Overview
- Configuration
- Supported Alert Channels
- Deployment Instructions
- Failure Types & Severities
- Testing
- Troubleshooting
- Container Probe Map (This Deployment)
- Version History
The watchdog runs two parallel monitoring paths:
| Path | Mechanism | What It Catches |
|---|---|---|
| Event stream | docker events (real-time) | Crashes (die), OOM-kills (oom), health status changes |
| Active Probes | docker ps + Probes | Integrity: Periodic verification of liveness, stuck states, and restart-loops |
The watchdog selects one of 4 active probe types per container, chosen automatically based on the container's configuration. Probe selection is cached for the container's lifetime and re-discovered on restart.
Docker HEALTHCHECK(passive) — for containers that have a
Docker HEALTHCHECKdirective in their Dockerfile. The watchdog reads the result fromcontainer.attrs["State"]["Health"]rather than running its own probe. When Docker reportshealth_status: unhealthy3 consecutive times, an alert fires. The last health check output is included in the alert's Detection field.HTTP GET(active) — for containers with no
Docker HEALTHCHECKthat expose HTTP ports. Auto-discovers a working endpoint by trying common paths (/-/healthy,/health,/healthz,/metrics,/) on each exposed internal port, and across all internal IPs when the container is attached to multiple Docker networks (configurable viaauto_health_check.pathsinwatchdog-config.yaml). Returns healthy ifstatus_code < 500. The discovered URL is cached for the container's lifetime.TCP connect(active) — if no HTTP path responds on a port, falls back to a raw
socket.create_connection()on that port. A successful connect confirms the service is listening; a refused or timed-out connection triggers anunhealthyalert. Used by databases, message brokers, and any non-HTTP service./procalive check(active) — last resort when the container exposes no ports at all, or all TCP connects fail during initial discovery. Reads/proc/1/statusinside the container viaexec_runand checks that PID 1 is in a live state (R/S/D). A zombie (Z) or stopped (T) PID 1 triggers anunhealthyalert.
Alerts are deduplicated via a cooldown — once an alert fires for a container, the same failure type is suppressed for cooldown_minutes minutes. The cooldown resets automatically when the container recovers.
Two additional deduplication rules suppress redundant alerts at the event level:
- OOM → die suppression: when a container is OOM-killed, Docker emits both an
oomevent and an immediately followingdieevent. The watchdog fires theoomCRITICAL alert and suppresses the subsequentcrashedalert to avoid a duplicate for the same incident. - Restart-loop → die suppression: once a
restart-loopHIGH alert is active for a container, subsequent per-cycledieevents are suppressed — the restart-loop alert is the primary signal until the container stabilises.
Recovery alerts: once a container that was previously alarmed (crashed, oom, unhealthy, or restart-loop) is observed running and healthy again, the watchdog dispatches a one-time INFO recovered alert through the same channels and clears its alarm state. Controlled by alert_on_recovery (default true). Detection depends on the same probe path used for the original failure — a container with a Docker HEALTHCHECK recovers via the health_status: healthy event; containers on active probes (HTTP/TCP//proc) recover on the next successful poll cycle.
.
├── watchdog.py # Main monitor script
├── watchdog-config.yaml # All configuration (no secrets)
├── docker-compose.yaml # Production / offline runtime (image only)
├── docker-compose.build.yaml # Developer / online build
├── Dockerfile # Image build definition (python:3.11-slim)
├── requirements-watchdog.txt # Python dependencies (reference; Dockerfile pip-installs inline)
├── install.sh # Offline install script
├── uninstall.sh # Stop + remove script
├── watchdog.tar # Pre-built Docker image (provided, no internet needed)
└── .env # Secrets — NOT committed to git
| Item | Size |
|---|---|
Docker image (watchdog:latest) | ~180 MB (python:3.11-slim base + dependencies) |
| Running container (memory) | ~50–80 MB |
watchdog.tar export | ~170 MB |
Note: The values above are approximate and may vary depending on the host operating system, Docker version, and installed dependencies.
docker==7.1.0
requests==2.32.3
PyYAML==6.0.2
pysnmp>=6.2
pysnmp 6.2+ uses the modern
pysnmp.hlapi.v3arch.asyncioAPI. pysnmp 4.x is no longer supported — its synchronous generator API was removed in 5.x.pyasn1is a transitive dependency managed by pysnmp itself and no longer needs to be pinned separately.
Runtime (in container): Python 3.11+
All non-secret settings live in watchdog-config.yaml. Secrets (webhook URLs, passwords) are stored in .env and never committed to version control. Restart the container after editing either file — no image rebuild is needed.
| Key | Default | Description |
|---|---|---|
alert_channels | ["slack"] | Active destinations: slack, smtp, snmp_trap |
check_interval_seconds | 60 | How often the poll loop runs |
cooldown_minutes | 5 | Suppress duplicate alerts per container per failure type |
restart_threshold | 5 | Restart count within window that triggers a restart-loop alert |
restart_window_minutes | 10 | Rolling window for restart counting |
unhealthy_cycles_threshold | 3 | Consecutive unhealthy cycles before alerting |
alert_on_recovery | true | Send an INFO "recovered" alert once a previously-alarmed container returns to normal |
excluded_containers | [] | Container names to never alert on |
log_level | INFO | DEBUG / INFO / WARNING / ERROR |
log_file | /var/log/watchdog/watchdog.log | Bind-mounted to ./watchdog/watchdog.log on host. Rotates at 10 MB, 5 backups. Set to null to disable |
runbook_base_url | — | URL included in every alert |
| Variable | Required | Description |
|---|---|---|
SLACK_WEBHOOK_URL | Yes (if Slack enabled) | Slack incoming webhook URL |
SMTP_USERNAME | Yes (if SMTP enabled) | SMTP username or login email used for login |
SMTP_PASSWORD | Yes (if SMTP enabled) | SMTP account password or api key value |
SNMP_V3_AUTH_KEY | Yes (if SNMPv3 enabled) | SNMPv3 authentication passphrase — minimum 8 characters (RFC 3414) |
SNMP_V3_PRIV_KEY | Yes (if SNMPv3 authPriv) | SNMPv3 privacy passphrase — minimum 8 characters; requires auth key also set |
WATCHDOG_CONFIG | No | Path to config file (default: /etc/watchdog/watchdog-config.yaml) |
WATCHDOG_HOST | No | Hostname shown in alerts (default: system hostname) |
Configure at least one channel and add its identifier to alert_channels in watchdog-config.yaml. Multiple channels can be active simultaneously.
Add slack to alert_channels and set SLACK_WEBHOOK_URL in .env:
slack:
enabled: truewebhook_url_env: SLACK_WEBHOOK_URLsyslog:
enabled: truehost: <syslog-server-ip>port: 514protocol: udp # udp or tcpfacility: local0Add smtp to alert_channels to enable.
smtp:
enabled: truehost: smtp.radware.comport: 587sender: noc-alerts@radware.comusername_env: SMTP_USERNAME # env var: SMTP username or app tokenrecipients:
- ops-team@radware.com
- oncall@radware.comtls: truepassword_env: SMTP_PASSWORD # env var: SMTP password or app tokenAdd snmp_trap to alert_channels to enable. Sends SNMP traps (v1, v2c, or v3) to your NMS/SIEM on every alert.
snmp_trap:
enabled: truehost: 155.1.1.4 # IP or hostname of your SNMP trap receiverport: 162# Standard SNMP trap portversion: v2c # SNMP version: v1, v2c, or v3community: public # SNMPv1/v2c community string (ignored for v3)trap_oid: "1.3.6.1.4.1.89.110.0.1"# Radware enterprise container-alert notification OID# SNMPv3 only — add SNMP_V3_AUTH_KEY / SNMP_V3_PRIV_KEY to .env# v3_username: watchdog-user# v3_auth_protocol: SHA # MD5 | SHA | SHA256 | SHA384 | SHA512# v3_auth_key_env: SNMP_V3_AUTH_KEY # min 8 chars# v3_priv_protocol: AES # AES (recommended) | AES192 | AES256 | DES (weak)# v3_priv_key_env: SNMP_V3_PRIV_KEY # min 8 chars; requires auth key also set# v3_local_engine_id: "" # hex engine ID pinned from first-run log; see DEPLOYMENT.mdEach trap carries five var-binds, all under the Radware enterprise OID arc (1.3.6.1.4.1.89.110):
| OID | Object | Value |
|---|---|---|
1.3.6.1.4.1.89.110.1.1.0 | cwHost | Hostname of the alerting node |
1.3.6.1.4.1.89.110.1.2.0 | cwSummary | Human-readable alert summary |
1.3.6.1.4.1.89.110.1.3.0 | cwContainerName | Container name |
1.3.6.1.4.1.89.110.1.4.0 | cwFailureType | crashed / oom / unhealthy / restart-loop / recovered |
1.3.6.1.4.1.89.110.1.5.0 | cwProbeDetail | Probe failure detail |
For full deployment instructions — including alert channel setup, offline installation, developer builds, and troubleshooting — see DEPLOYMENT.md.
Quick start (automated):
bash install.sh| Failure | Severity | Trigger |
|---|---|---|
crashed | CRITICAL | Container exited with non-zero exit code |
oom | CRITICAL | Container was OOM-killed by the kernel |
unhealthy | HIGH | Health probe failing for N consecutive cycles |
restart-loop | HIGH | Container restarted ≥ threshold times within window |
recovered | INFO | Previously-alarmed container (crashed/oom/unhealthy/restart-loop) is healthy/running again. Fires once per incident; controlled by alert_on_recovery (default true) |
OOM alerts include memory stats (usage / limit / peak) prepended to the log snippet.
Every alert includes two probe fields that identify exactly how and why the failure was detected:
- Probe Type — short label classifying the detection mechanism
- Detection — full detail string with path, status code, or error message
| Failure | Probe Type | Example Detection value |
|---|---|---|
crashed | Docker event (crash) | Crash probe failed — container exited with exit code 1 |
oom | Docker event (OOM) | OOM probe failed — container was OOM-killed by the kernel |
unhealthy (Docker native HEALTHCHECK) | Docker HEALTHCHECK | Docker health probe failed — exit code 1, output: connection refused |
unhealthy (HTTP auto-probe) | HTTP GET | HTTP probe failed — error response 503 Service Unavailable, URI http://172.18.0.5:8080/health |
unhealthy (TCP connect) | TCP connect | TCP probe failed — connection refused or timed out on 172.18.0.5:6379 (Connection refused) |
unhealthy (/proc fallback) | /proc alive check | /proc alive check failed — PID 1 is zombie/stopped (exit code 1) |
Restart-loop detection is not a probe — it is tracked by the Poll loop path and fires when a container restarts ≥
restart_thresholdtimes withinrestart_window_minutes. See the Poll loop row in the monitoring paths table above.
The sections below describe optional manual tests that validate the watchdog's detection and alerting paths. Each creates a temporary Docker container and cleans up after itself.
Two options — choose based on whether the host has internet access.
Pulls
polinux/stressfrom Docker Hub.
# --memory-swap=32m disables swap so the kernel is forced to OOM-kill rather than swap out# Note: the polinux/stress entrypoint IS the stress binary — do not repeat "stress" in the args
docker run -d --name oom-test --memory=32m --memory-swap=32m polinux/stress \
--vm 1 --vm-bytes 64M --vm-keep# Clean up
docker rm -f oom-test
docker rmi polinux/stressUses the Watchdog image that is already present on the host, avoiding the need to pull the image during deployment.
docker run -d --name oom-test --memory=32m --memory-swap=32m watchdog:latest \
python3 -c "x = b'\xff' * (64 * 1024 * 1024)"# Clean up — container only (keep the watchdog image)
docker rm -f oom-testDocker kills the container with exit code 137 and emits an oom event. The watchdog captures memory stats and the last 20 log lines, then dispatches a CRITICAL alert — all within seconds of the kill.
If the container exits cleanly (exit code 0) instead of being OOM-killed, cgroup memory accounting may be disabled on this host. Verify with:
docker info 2>&1| grep -i "memory limit"
WARNING: No memory limit supportmeans the--memoryflag is silently ignored. Enable cgroup memory accounting by addingcgroup_enable=memory swapaccount=1toGRUB_CMDLINE_LINUXin/etc/default/grub, then runsudo update-gruband reboot.
Optional — requires internet access. These tests pull images from Docker Hub and create temporary containers on the host. Each section includes full cleanup instructions (container + image).
# Start a container where every HTTP path returns 503# Note: the full server { } block is required — a bare location { } directive is not valid nginx config
docker run -d --name http-probe-test \
nginx:alpine \
sh -c "printf 'server { listen 80; location / { return 503; } }' > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"# Verify the container is running:
docker ps | grep http-probe-test
# Wait one poll cycle (60s), then check watchdog detected it:
grep "http-probe-test" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
# Clean up
docker rm -f http-probe-test
docker rmi nginx:alpineDuring auto-discovery the watchdog sends an HTTP GET to /-/healthy on port 80. Nginx returns 503, so the watchdog immediately caches that endpoint as failing and reports unhealthy. After unhealthy_cycles_threshold consecutive unhealthy cycles an alert fires with Detection value HTTP probe failed — error response 503 Service Unavailable, URI http://<ip>:80/-/healthy.
# Expose port 9999 but run nothing on it — TCP connect will be refused
docker run -d --name tcp-probe-test \
--expose 9999 \
alpine \
sh -c "sleep infinity"# Watch the watchdog fall through HTTP → TCP and alert:
grep "tcp-probe-test" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
# Clean up
docker rm -f tcp-probe-test
docker rmi alpineAfter unhealthy_cycles_threshold consecutive failed cycles an alert fires with Detection value TCP probe failed — connection refused or timed out on <ip>:9999.
# No EXPOSE — watchdog falls straight to /proc alive check
docker run -d --name proc-probe-test \
alpine \
sleep infinity
# Verify the container is running:
docker ps | grep proc-probe-test
# After one poll cycle, confirm the watchdog selected /proc as the probe type:
grep "proc-probe-test" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
# Clean up
docker rm -f proc-probe-test
docker rmi alpineWith no exposed ports, auto-discovery skips HTTP and TCP and falls back to reading /proc/1/status inside the container via exec_run. Since sleep is PID 1 in a live S (sleeping) state, no alert fires. The log will show falling back to /proc alive-check confirming the probe type was selected. In production, this probe fires when PID 1 enters zombie (Z) or stopped (T) state.
Validates the INFO recovered alert fires once a previously-alarmed container is healthy again. Use any real, already-monitored container — no throwaway container needed.
# 1. Force a failure (SIGKILL → non-zero exit code → CRITICAL "crashed" alert)
docker kill<container-name># 2. Confirm the crashed alert was logged/dispatched
grep "<container-name>" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log | grep CRITICAL
# 3. Bring it back
docker start <container-name># 4. Wait for it to report healthy again, then confirm the recovery alert:
grep "<container-name>" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log | grep -i recoveredExpected log line: ALERT [INFO] <container-name> — recovered (channels: [...]).
- Containers with a Docker
HEALTHCHECKrecover via thehealth_status: healthyevent — near-instant once Docker reports healthy (subject to the image's ownstart_period/interval). - Containers on HTTP/TCP/
/procactive probes recover on the next poll cycle (check_interval_seconds). - No alert fires if
alert_on_recovery: falseis set, or if the container was never in an alarmed state (state.alerted_forempty).
docker compose logs docker-container-watchdogCommon causes:
- Missing
.envfile — runcp .env.example .envand fill in credentials - Docker socket not accessible — ensure
/var/run/docker.sockexists and the container has read access - Image not loaded — run
docker images watchdog; if empty, build withdocker compose -f docker-compose.build.yaml build(ordocker build -t watchdog:latest .directly — see DEPLOYMENT.md for details)
- Check
alert_channelsinwatchdog-config.yaml— ensure at least one channel is listed and configured - Check watchdog logs for errors:
grep -E "ERROR|CRITICAL" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log - For Slack: verify
SLACK_WEBHOOK_URLis set in.envand test manually:Expected response:curl -X POST -H 'Content-type: application/json' \ --data '{"text":"Watchdog test"}'"$SLACK_WEBHOOK_URL"
ok
Recovery alert (recovered) specifically not received:
- Confirm
alert_on_recoveryis not set tofalsein the livewatchdog-config.yaml. - Confirm the container was actually alarmed first —
state.alerted_formust be non-empty; a container that was never flagged won't generate a recovery alert. - For containers with a Docker
HEALTHCHECK, confirm Docker actually reportshealthy—docker inspect <name> --format '{{json .State.Health}}'. If it's stuck onstarting, the watchdog is waiting on Docker, not the other way around. - For containers on active probes (HTTP/TCP/
/proc), confirm the poll loop reportshealth=healthyfor the container in the log — if it staysunhealthy, the probe (oftenauto_health_check) is misjudging the service; add a manual entry undercontainer_health_checksinstead.
Increase cooldown_minutes in watchdog-config.yaml (default: 5 minutes per container per failure type). To silence a noisy container entirely, add it to excluded_containers:
excluded_containers:
- debug-shell
- load-test
- my-noisy-containerCheck which probe was cached at startup:
grep -E "auto-discover|falling back" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.logLog destinations (simultaneous):
- stdout — always on; captured by
docker compose logs - file —
./watchdog/watchdog.logon the host (bind-mounted), rotates at 10 MB, keeps 5 backups - syslog — remote server if
syslog.enabled: truein config
# Live tail (preferred — host path, no need to exec into the container)
tail -f /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
# Last 100 lines
tail -100 /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
# Alerts and errors only
grep -E "ALERT|ERROR|CRITICAL" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
# Filter by container name
grep "my-container" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
# Search across rotated logs
grep "CRITICAL" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log \
/opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log.1
# Via Docker (stdout only)
docker compose logs -f docker-container-watchdogProbe selection is automatic: containers with a Docker HEALTHCHECK are monitored passively via events; all others get an active probe auto-discovered on the first poll and cached for the container's lifetime.
| Container | Probe Type | Endpoint | Why |
|---|---|---|---|
config_kvision-infra-fluentd_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-dp-inline-config_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-collector_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-configuration-service_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-anomaly-detection-engine_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-data-persist-service_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-rt-alert_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_policy-editor_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-alerts_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-reporter_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-vrm_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-infra-redis_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-health_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-webui_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-tor-feed_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-infra-efk_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-data-polling-mgr_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-formatter_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-data-polling-scheduler_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-infra-cadvisor_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-scheduler_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-vdirect_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-help_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-ted_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_postgres_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-snmp-trap-collector_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-auto-engine_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-lls_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-assist-service_1 | HTTP | :3005 (path auto-discovered) | No Docker HEALTHCHECK; Node.js service; path cached on first successful response |
config_kvision-infra-rabbitmq_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
config_kvision-ha-operator_1 | HTTP | :8080 (path auto-discovered) | No Docker HEALTHCHECK; Java HTTP service; path cached on first successful response |
config_kvision-dc-nginx_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
process-exporter | HTTP | :9256/metrics | No Docker HEALTHCHECK; Prometheus exporter — /metrics returns 200 |
prometheus | HTTP | :9090/-/ready | No Docker HEALTHCHECK; standard Prometheus readiness endpoint |
alertmanager | HTTP | :9093/-/healthy | No Docker HEALTHCHECK; standard Alertmanager health endpoint |
postgres-exporter | HTTP | :9187/metrics | No Docker HEALTHCHECK; Prometheus exporter — /metrics returns 200 |
mysql-exporter | HTTP | :9104/metrics | No Docker HEALTHCHECK; Prometheus exporter — /metrics returns 200 |
es-exporter | HTTP | :9114/metrics | No Docker HEALTHCHECK; port 7979 HTTP fails; port 9114 serves /metrics |
node-exporter | HTTP | :9100/metrics | No Docker HEALTHCHECK; Prometheus exporter — /metrics returns 200 |
grafana | HTTP | :3000/api/health | No Docker HEALTHCHECK; Grafana health endpoint returns 200 |
config_kvision-infra-mariadb_1 | Docker HEALTHCHECK | Docker managed | Image has HEALTHCHECK directive |
Verify what was actually cached after startup:
grep -E "auto-discover|falling back" /opt/radware/storage/scripts/Alert_Container/watchdog/watchdog.log
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.4.0 | 2026-08-17 | Rahul Kumar | Added INFO "recovered" alert |
| 1.3.3 | 2026-08-05 | Rahul Kumar | Added Auth True/false |
| 1.3.2 | 2026-07-28 | Rahul Kumar | Fixed SMTP Auth issue |
| 1.3.1 | 2026-07-21 | Egor Egorov | Updated Readme and Deployment guides |
| 1.3.0 | 2026-07-16 | Rahul Kumar | Added Upgrade Guide |
| 1.2.0 | 2026-07-16 | Rahul Kumar | Removed sudo and renamed the container |
| 1.1.0 | 2026-07-09 | Rahul Kumar | Added SNMPv3 support |
| 1.0.0 | 2026-06-25 | Rahul Kumar | Added Slack notifications and bug fixes |