How to Watch Any Web Page for Changes with changedetection.io (Prices, Stock, Appointments)

The GPU you want keeps selling out. The council publishes bin-collection changes somewhere on a page. The GP's booking page opens slots at random times. changedetection.io checks pages on a schedule, compares them to last time, and sends a notification when the bit you care about changes — with a visual selector so "the bit you care about" is the price, not the rotating banner. One container; a browser container alongside for sites that need JavaScript.
Quick reference
| Task | What |
|---|---|
| Folder | ~/docker/changedetection/compose.yaml |
| Web | http://server-ip:5000 (or another port if Frigate has it) |
| Add a watch | paste a URL → Edit → Visual selector → click the element |
| JavaScript sites | add the browser-chrome service and pick Playwright in the watch |
| Alerts | Notification URLs in Apprise format: ntfy, Telegram, email |
| Check interval | per watch; be polite — 30 minutes or more for most |
1. The compose file
mkdir -p ~/docker/changedetection/data && cd ~/docker/changedetection
nano compose.yaml
services:
changedetection:
image: ghcr.io/dgtlmoon/changedetection.io:latest
container_name: changedetection
ports:
- "5001:5000"
environment:
PLAYWRIGHT_DRIVER_URL: ws://browser-chrome:3000/
BASE_URL: http://192.168.1.50:5001
TZ: Europe/London
volumes:
- ./data:/datastore
depends_on:
- browser-chrome
restart: unless-stopped
browser-chrome:
image: dgtlmoon/sockpuppetbrowser:latest
container_name: browser-chrome
environment:
SCREEN_WIDTH: 1920
SCREEN_HEIGHT: 1080
MAX_CONCURRENT_CHROME_PROCESSES: 5
restart: unless-stopped
docker compose up -d
sudo ufw allow from 192.168.1.0/24 to any port 5001
2. A first watch
Open http://192.168.1.50:5001, paste a product page's URL, Watch. It fetches the page and shows the text it found. Click Edit → Visual Filter Selector: the page renders as a picture; click the price, and the selector (span.price) is filled in. Now only that element is compared. Recheck once, and the watch is live.
Settings → Time between check: 30 minutes for a shop, daily for a council page. Checking every minute will get your IP blocked and is unkind.
3. JavaScript-only pages
If the fetched text is empty or "enable JavaScript", switch the watch's Fetch method to Playwright Chromium — the browser container renders it properly. It can also click a button or fill a form first (Browser Steps), for pages behind a "check availability" click.
4. Alerts
Settings → Notifications (global) or per watch, in Apprise URL form:
ntfys://TOKEN@ntfy.example.com/server
tgram://BOT_TOKEN/CHAT_ID
mailto://myserver@gmail.com:APP_PASSWORD@gmail.com?to=you@example.com
The message body can include {{diff}} — the changed text — so the notification says "£799 → £649" rather than "something changed". Send test proves it.
5. Useful tricks
- Trigger text: only notify if the new text contains "In stock" or "Available".
- Ignore text: skip lines like "Last updated" that change every time.
- Price tracking: Restock & Price detection mode understands shop pages and notifies on drops below a threshold.
- RSS: the whole watch list is an RSS feed for FreshRSS (tutorial 89).
- Proxies: route some watches through a VPN container (Gluetun, tutorial 136) if a site blocks your home IP.
- Groups and tags: Shopping, Council, Work — with different notification targets.
Things it's good for
GPU and console stock, used-car listings, a flat on Rightmove with a price filter, a concert's tickets page, software release pages, your own site's homepage (as a cheap "did it get defaced" check), a competitor's prices, the school term dates page.
Troubleshooting
Every check reports a change. Something dynamic is inside the selector — narrow it, or add the changing text to Ignore text.
"Fetch failed" / 403. The site blocks bots. Use Playwright, set a browser user-agent in the watch's Request tab, lengthen the interval, or go through a proxy.
Port 5000 clash. Frigate (91) uses it; the compose above maps to 5001.
Browser container eats memory. Lower MAX_CONCURRENT_CHROME_PROCESSES, or use plain fetch for sites that don't need JavaScript.
Wrapping up
One container (two with the browser), a URL, a click on the element you care about, an ntfy URL. The page that you'd otherwise refresh every evening now tells you — the moment the price drops, the stock returns, or the slot appears.
