WAN IP monitoring with Discord, Telegram, Email alerts. Lightweight & Docker-ready for homelabs!
3.5K

Monitors your WAN IPv4/IPv6 addresses and tells you when they change. When an address changes it can notify you, update DNS records, publish the state over MQTT for Home Assistant, and expose a status API with Prometheus metrics. It is aimed at homelabs and small servers on connections where the ISP changes your IP.
<NAME>_FILE), plus Trivy scanning, CycloneDX SBOM and Cosign keyless image signing.LOG_FORMAT=json) and adaptive backoff with jitter.Coming from 1.x, the one breaking change is that the container runs as uid 1000,
so the /data and /logs volumes must be writable by that user. See the
upgrade guide.
Notifications
APPRISE_URLS settingIP detection
Dynamic DNS
Observability
/healthz, /api/status (JSON), and Prometheus /metricsEvents
Operations
| Architecture | Tags | Status |
|---|---|---|
| x86-64 (AMD64) | latest, 2.5.0 | Supported |
| ARM64 (aarch64) | latest, 2.5.0 | Supported |
Docker pulls the correct image for your platform automatically. ARM64 covers Raspberry Pi 4 and newer, Apple Silicon, and AWS Graviton.
Create the host directories and make them writable by uid 1000:
mkdir -p data logs
sudo chown -R 1000:1000 data logs
docker run -d \
--name wanwatcher \
--restart unless-stopped \
-e DISCORD_ENABLED="true" \
-e DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN" \
-e SERVER_NAME="My Server" \
-v ./data:/data \
-v ./logs:/logs \
noxied/wanwatcher:2.5.0
services:
wanwatcher:
image: noxied/wanwatcher:2.5.0
container_name: wanwatcher
restart: unless-stopped
environment:
SERVER_NAME: "My Server"
CHECK_INTERVAL: "900"
DISCORD_ENABLED: "true"
DISCORD_WEBHOOK_URL: "https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN"
volumes:
- ./data:/data
- ./logs:/logs
docker compose up -d
Everything is set through environment variables. Booleans are the string
"true" (anything else is false). Lists are comma separated. At least one
notification method must be enabled.
DISCORD_ENABLED: "true"
DISCORD_WEBHOOK_URL: "https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN"
DISCORD_AVATAR_URL: "" # optional; the webhook's own avatar is used when empty
TELEGRAM_ENABLED: "true"
TELEGRAM_BOT_TOKEN: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
TELEGRAM_CHAT_ID: "123456789"
TELEGRAM_PARSE_MODE: "HTML"
EMAIL_ENABLED: "true"
EMAIL_SMTP_HOST: "smtp.gmail.com"
EMAIL_SMTP_PORT: "587" # 587 for TLS, 465 for SSL
EMAIL_SMTP_USER: "[email protected]"
EMAIL_SMTP_PASSWORD: "app-password"
EMAIL_FROM: "[email protected]"
EMAIL_TO: "[email protected]" # comma separated for multiple recipients
EMAIL_USE_TLS: "true"
One setting opens 100+ services. See the Apprise URL list.
APPRISE_ENABLED: "true"
APPRISE_URLS: "ntfy://ntfy.sh/my-topic,pover://user@token"
DDNS_ENABLED: "true"
DDNS_PROVIDER: "cloudflare" # cloudflare | duckdns | dyndns2 | route53
# Cloudflare
CLOUDFLARE_API_TOKEN: "token-with-zone-dns-edit"
CLOUDFLARE_ZONE: "example.com"
CLOUDFLARE_RECORDS: "home.example.com,vpn.example.com"
CLOUDFLARE_PROXIED: "false"
# DuckDNS
DUCKDNS_TOKEN: "your-token"
DUCKDNS_DOMAINS: "mysub"
# dyndns2 (No-IP, Dynu, and compatibles)
DYNDNS2_SERVER: "https://dynupdate.no-ip.com"
DYNDNS2_USERNAME: "user"
DYNDNS2_PASSWORD: "password"
DYNDNS2_HOSTNAMES: "host.example.com"
# AWS Route53 (creds need route53:ChangeResourceRecordSets on the zone)
ROUTE53_ACCESS_KEY_ID: "AKIA..."
ROUTE53_SECRET_ACCESS_KEY: "your-secret"
ROUTE53_HOSTED_ZONE_ID: "Z1234567890ABC"
ROUTE53_RECORDS: "home.example.com"
API_ENABLED: "true"
API_PORT: "8080" # publish this port to reach the API
MQTT_ENABLED: "true"
MQTT_HOST: "192.168.1.10"
MQTT_PORT: "1883"
MQTT_USERNAME: "wanwatcher"
MQTT_PASSWORD: "secret"
MQTT_HA_DISCOVERY: "true" # sensors appear in Home Assistant automatically
CHECK_INTERVAL: "900" # seconds between checks (minimum 60)
MONITOR_IPV4: "true"
MONITOR_IPV6: "true"
IP_CHANGE_CONFIRMATION: "true" # confirm a change with a second source
IPINFO_TOKEN: "" # optional ipinfo.io token for geo data
NOTIFY_ON_STARTUP: "true"
HEARTBEAT_ENABLED: "false"
HEARTBEAT_INTERVAL: "86400"
OUTAGE_DETECTION_ENABLED: "true"
OUTAGE_THRESHOLD: "3"
UPDATE_CHECK_ENABLED: "true"
UPDATE_CHECK_INTERVAL: "86400"
The full reference with every variable and default is in the README.
| Mount | Purpose |
|---|---|
/data | State file with the current IPs and change history (persistent) |
/logs | Application logs |
Port 8080 only needs publishing when API_ENABLED=true.
The image ships a healthcheck. With the status API enabled it queries
/healthz; otherwise it confirms the state file is valid JSON and was
refreshed within the expected interval, so a stuck loop is reported as
unhealthy.
docker inspect --format='{{json .State.Health}}' wanwatcher
View logs:
docker logs -f wanwatcher
Read the current state:
docker exec wanwatcher cat /data/ipinfo.db
With API_ENABLED=true, query the status and metrics:
curl http://localhost:8080/api/status
curl http://localhost:8080/metrics
docker compose:
docker compose pull
docker compose up -d
docker run:
docker pull noxied/wanwatcher:latest
docker stop wanwatcher && docker rm wanwatcher
# re-run your docker run command
See the upgrade guide for version-specific notes, including the uid 1000 change in 2.0.
Container exits immediately
docker logs wanwatcher for the validation output. Common causes: no notification method enabled, an invalid webhook URL, or a missing required variable.Permission denied on /data or /logs after upgrading to 2.0
sudo chown -R 1000:1000 ./data ./logs on the host directories.Notifications not sending
docker logs wanwatcher | grep -i "notification\|retry" and verify credentials.IPv6 not detected
MONITOR_IPV6="true".More help: troubleshooting guide.
AMD64:
docker build -t wanwatcher:local .
Multi-arch (requires buildx):
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t wanwatcher:local \
--push .
| Tag | Meaning |
|---|---|
2.5.0 | This exact release |
2.0 | Latest 2.0.x patch |
2 | Latest 2.x release |
latest | Latest stable release |
| Version | Date | Highlights |
|---|---|---|
| 2.5.0 | 2026-06-13 | Reliability hardening (/healthz staleness, isolated side-effect failures) |
| 2.4.1 | 2026-06-13 | Security: escape untrusted strings in notifications |
| 2.3.0 | 2026-06-13 | AWS Route53 DDNS provider |
| 2.2.0 | 2026-06-13 | Secrets from files, Trivy/SBOM/Cosign supply-chain security |
| 2.1.0 | 2026-06-13 | Optional JSON logging (LOG_FORMAT), adaptive backoff with jitter |
| 2.0.0 | 2026-06-11 | Package rewrite, Apprise, multi-source detection, DDNS, status API and metrics, MQTT and Home Assistant, events, non-root container |
| 1.4.1 | 2025-11-02 | Python 3.14, code quality and security fixes, smaller image |
| 1.4.0 | 2025-11-02 | Configuration validation, notification retry, expanded tests |
| 1.3.0 | 2025-10-27 | Email notifications, update checking, custom avatars |
| 1.2.0 | 2025-10-27 | Telegram support, IPv6 monitoring |
MIT licensed.
Content type
Image
Digest
sha256:c7a77f8af…
Size
50.4 MB
Last updated
about 1 month ago
docker pull noxied/wanwatcher