The Problem: You Miss Things That Matter
There’s a specific kind of frustration that comes from checking a product page every day for two weeks — manually, in a browser tab — only to discover the item went back in stock and sold out again while you were asleep. Or watching a competitor’s pricing page, a government form, a job listing. At some point, manual monitoring stops being a workflow and starts being a second job.
The root cause isn’t laziness. Websites just don’t notify you when their content changes. RSS feeds are half-dead. Email newsletters are curated for marketing, not for tracking specific page content. Browser extensions that do this tend to be flaky, tied to a single machine, and privacy-questionable.
What you actually need is a self-hosted service running 24/7 in your HomeLab. Something that checks URLs on a schedule, diffs the content, and pushes an alert to wherever you’re already paying attention. That’s exactly what Changedetection.io does, and running it in Docker takes about 10 minutes.
What Changedetection.io Actually Does
Changedetection.io is an open-source web content monitoring tool. It fetches pages at configurable intervals, compares the new content against what it saw last time, and fires off a notification when differences are detected.
Here’s what makes it worth using over a simple browser extension:
- Browser-based rendering support — via Playwright, it handles JavaScript-heavy SPAs that a plain HTTP fetch would return blank for.
- XPath/CSS selector filtering — scope monitoring to a specific section of a page, ignoring nav bars and footers that change constantly.
- Notification integrations — Telegram, Slack, Discord, email, ntfy, Pushover, and more, all via Apprise under the hood.
- Change history — every detected change is stored with a diff view so you can see exactly what moved.
- Visual diff — highlights added/removed text directly in the web UI.
Running it via Docker Compose gives you a persistent, always-on monitor. No browser tab left open anywhere.
Setting Up Changedetection.io with Docker Compose
Prerequisites
You need Docker and Docker Compose installed on your HomeLab host. On Ubuntu/Debian:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
Verify it’s working:
docker compose version
Create the Project Directory
mkdir -p ~/homelab/changedetection/datastore
cd ~/homelab/changedetection
Write the Docker Compose File
Create docker-compose.yml with the content below. This setup bundles in the Playwright browser container for JavaScript-rendered pages. If you’re only monitoring static sites, drop the playwright-chrome service and the PLAYWRIGHT_DRIVER_URL env var — it’ll save you around 300–500MB of RAM.
services:
changedetection:
image: ghcr.io/dgtlmoon/changedetection.io
container_name: changedetection
hostname: changedetection
volumes:
- ./datastore:/datastore
environment:
- PORT=5000
- PLAYWRIGHT_DRIVER_URL=ws://playwright-chrome:3000
ports:
- "5000:5000"
depends_on:
playwright-chrome:
condition: service_started
restart: unless-stopped
playwright-chrome:
image: dgtlmoon/sockpuppetbrowser:latest
container_name: playwright-chrome
hostname: playwright-chrome
cap_add:
- SYS_ADMIN
restart: unless-stopped
environment:
- SCREEN_WIDTH=1920
- SCREEN_HEIGHT=1024
- SCREEN_DEPTH=16
- MAX_CONCURRENT_CHROME_PROCESSES=10
Start the Services
docker compose up -d
Check that both containers are running:
docker compose ps
You should see changedetection and playwright-chrome both in the running state. Open http://<your-homelab-ip>:5000 in your browser.
Add Your First Watch
On the main dashboard, paste any URL into the input field and click Watch. Changedetection.io fetches the page immediately and stores a baseline snapshot. On every subsequent check (default interval: 24 hours, adjustable per-watch), it diffs against that baseline.
To change the interval for a specific watch, click the watch name → Edit → set the Time between checks field. For pricing or stock pages, somewhere between 30 minutes and 2 hours is a reasonable range — frequent enough to catch changes, gentle enough not to hammer the target server.
Configuring Telegram Notifications
Create a Telegram Bot
- Open Telegram and search for @BotFather.
- Send
/newbot, follow the prompts, and copy the HTTP API token it gives you. - Send any message to your new bot to initialize the chat.
- Fetch your chat ID by running this in a terminal (replace
TOKENwith your actual bot token):
curl -s "https://api.telegram.org/botTOKEN/getUpdates" | python3 -m json.tool
Look for "chat":{"id": 123456789} in the response. That number is your chat ID.
Add the Notification URL in Changedetection.io
Changedetection.io uses Apprise for notifications. The Telegram format is:
tgram://BOT_TOKEN/CHAT_ID
Go to Settings → Notifications in the web UI, paste your Apprise URL, and click Send test notification. A test message should land in Telegram within a few seconds.
You can also set notification URLs per-watch under Edit → Notifications. Handy if you want certain watches to alert a group chat while others go to your personal DMs.
Customize the Notification Message
The default message template works fine out of the box. To tweak it, go to Settings → Notifications → Notification body. The most useful variables:
{{watch_url}}— the monitored URL{{watch_title}}— the label you gave the watch{{diff}}— a text diff of what changed{{preview_url}}— link to the diff view in your Changedetection instance
A template that works well for Telegram:
🔔 Change detected: {{watch_title}}
URL: {{watch_url}}
Preview: {{preview_url}}
Hands-On: Monitoring a JavaScript-Rendered Page
Some pages load their actual content after the initial HTML response — React or Vue apps that render in the browser, for example. A plain HTTP fetch returns an empty shell. This is where the Playwright container earns its keep.
When adding or editing a watch, scroll to Request → Fetch backend and switch from Basic fast Fetch to Chrome/Javascript. Changedetection.io routes that watch through the Playwright browser, which fully renders the page before taking a snapshot.
Pair that with a CSS/XPath filter under Filters & Triggers to watch only the element you care about — say, a product price:
css:.product-price
Now everything else on the page is ignored. You won’t get pinged every time an ad rotates or a cookie banner updates. Only when the price itself moves.
Keeping the Data Safe
All watch configurations and change history live in ./datastore on your host. Backing it up is one command:
tar -czf changedetection-backup-$(date +%Y%m%d).tar.gz ./datastore
Moving to a different host is just as easy — copy the datastore directory, bring up the containers, and everything is there: watches, history, notification settings, all of it.
I’ve run this setup for monitoring a handful of vendor pricing pages and internal dashboards. Rock solid. The Playwright container does add memory overhead — budget around 512MB–1GB total depending on concurrent checks — but on any homelab box with 4GB+ RAM, that’s not a concern.
Where to Go From Here
At this point you have a self-hosted monitor that checks URLs on your schedule, handles JavaScript rendering, scopes changes to exactly the content you care about, and pushes Telegram alerts the moment something shifts. No open browser tabs. No missed restocks.
Two natural next steps: first, put a reverse proxy in front of it — Nginx Proxy Manager, Caddy, or Traefik all work — so you can reach the UI over HTTPS without exposing port 5000 directly. Second, add basic authentication if the instance is reachable from outside your home network. Neither is complicated, and both are worth doing before you start monitoring anything sensitive.

