Self-host changedetection.io to watch web pages
Follow this on your own server. Start free, no card needed
Most web pages worth watching don’t have a feed, an API, or any way to ask “did this change” other than loading it yourself and comparing. changedetection.io watches any page, or a specific element on it that you pick visually, and tells you when it changes: a price drop, something back in stock, a government notice page updating, a competitor’s pricing page moving. The alternative is paying for a change-monitoring SaaS tool or writing your own scraper-and-cron script that breaks the first time the page’s markup shifts. By the end of this guide you’ll have your own changedetection.io instance running on a $6/month server, reachable over HTTPS at your own domain, with its history backed up and an upgrade path that doesn’t lose your watches.
What you’ll need
- A server. DigitalOcean’s Basic Droplet at $6.00/month gets you 1 vCPU, 1 GB RAM and 25 GB SSD, more than enough for this single-container app plus a reverse proxy. Any Ubuntu 24.04 VPS from another provider works the same way.
- A domain name (or a subdomain of one you already own), with access to its DNS settings.
- Docker and the Docker Compose plugin installed on the server (covered in Step 2).
- A terminal and SSH access to the server.
Step 1: Create the server and point a domain at it
Spin up an Ubuntu 24.04 droplet on DigitalOcean’s $6/month Basic plan (1 vCPU, 1 GB RAM, 25 GB SSD). Note its public IP address.
In your domain’s DNS settings, add an A record pointing a subdomain, for example watch.yourdomain.com, at that IP:
Type: A
Name: watch
Value: <your server's IP>
TTL: 300
DNS propagation can take a few minutes to a few hours. You should see dig watch.yourdomain.com +short return your server’s IP before moving on, since the next steps depend on it.
Step 2: Install Docker
SSH into the server and install Docker Engine plus the Compose plugin:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Log out and back in for the group change to take effect, then check both pieces are present:
docker --version
docker compose version
You should see a Docker version and a Compose version printed, with no “command not found” errors.
Step 3: Lay out the project directory
Create a directory for the stack:
mkdir -p ~/changedetection && cd ~/changedetection
That’s genuinely it for secrets at this stage. changedetection.io doesn’t require an account, an API key, or a database password to start watching pages. If you want to add a login password so the instance isn’t open to anyone who finds the URL, that’s a setting inside the app itself, on its Settings page, once it’s running, not something you configure ahead of time. Worth doing if this server is reachable by more than just you; skippable if it isn’t.
Step 4: Write the Docker Compose file
changedetection.io keeps everything, its watch configuration, page history and snapshots, on disk in a single volume, so there’s no separate database service to run. Create docker-compose.yml:
services:
changedetection:
image: dgtlmoon/changedetection.io:0.60.9
container_name: changedetection
hostname: changedetection
restart: unless-stopped
volumes:
- changedetection-data:/datastore
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- changedetection
volumes:
changedetection-data:
caddy_data:
caddy_config:
A couple of choices worth explaining:
- The image is pinned to
0.60.9rather than left floating onlatest. You decide when to move to a newer release, covered in Step 7. - Only Caddy publishes ports to the host. changedetection.io itself isn’t exposed to the internet directly, it’s reachable only on the internal Docker network, on its default port 5000.
Before starting anything, validate the file parses:
docker compose -f docker-compose.yml config
A clean run prints the fully resolved configuration back with no errors.
Step 5: Write the Caddyfile and start everything
Caddy handles the TLS certificate from Let’s Encrypt automatically, as long as DNS is already pointing at the server and ports 80 and 443 are reachable from the internet. Create Caddyfile in the same directory:
watch.yourdomain.com {
reverse_proxy changedetection:5000
}
Validate it before starting:
docker run --rm -v $(pwd)/Caddyfile:/etc/caddy/Caddyfile caddy:2 caddy validate --config /etc/caddy/Caddyfile
A clean run reports the config as valid. Then start the stack:
docker compose up -d
Watch it come up and check Caddy actually got a certificate:
docker compose logs -f caddy
You should see a line mentioning a certificate obtained for your domain.
Step 6: Add your first watch
Visit https://watch.yourdomain.com. There’s no account-creation step, no sign-up form: the first screen lets you paste a URL and add it as a watch directly. Add a page you actually want to track, optionally narrow it to one element with the visual selector if the page has a lot of noise around the part you care about, and save it.
If this instance is reachable by anyone besides you, go to Settings inside the app and set a login password. It’s optional, not required to get going, but worth doing the moment the URL isn’t just yours.
A couple of settings worth knowing about from the start, without needing to touch either of them yet: LOGGER_LEVEL controls how noisy the container’s own logs are (TRACE, DEBUG, INFO, SUCCESS, WARNING, ERROR, CRITICAL, defaulting to DEBUG), and watching pages that only render their real content via JavaScript needs an additional headless browser component the project calls sockpuppetbrowser, a separate container, which is out of scope for this basic setup. The project’s own documentation covers adding it if you later need it.
Step 7: Back up your watches and history
The one named volume, changedetection-data, holds everything: every watch you’ve configured, its full history, and every stored snapshot. Back it up the same way as any other Docker volume, with a tarball kept off the server:
docker run --rm -v changedetection_changedetection-data:/data -v $(pwd):/backup alpine \
tar czf /backup/changedetection-backup-$(date +%F).tar.gz -C /data .
Put that in a cron job and copy the result somewhere other than this server, an object storage bucket, another machine, anywhere that isn’t this droplet. A backup that lives only on the server it’s backing up doesn’t survive the failure mode that actually destroys a server.
Step 8: Upgrade without losing your watches
Check the version you’re currently running, then bump the pinned tag in docker-compose.yml:
image: dgtlmoon/changedetection.io:<new-version>
Pull and restart just that service:
docker compose pull changedetection
docker compose up -d changedetection
Check the logs for a clean startup:
docker compose logs changedetection
Then confirm your existing watches and their history are still there when you open the app. If anything looks wrong, your backup from Step 7 is the way back.
The shortcut: do it with Railyard
Everything above, a persistent /datastore volume and HTTPS already wired up, is also a one-click template:
https://app.railyard.run/deploy?template=changedetection
Railyard builds this one from the project’s own Dockerfile in its GitHub repository, the same approach as the manual setup above just automated, rather than pulling a pre-built image. No addons: no database, no extra service, since changedetection.io doesn’t need one for this baseline setup either.
If you’d rather start from Railyard generally before deciding on changedetection.io specifically, the quickstart covers the basics, and databases and backups explains how backups work across every template, not just this one. Deploy button covers the ?template= link format itself if you want to embed one elsewhere. Railyard’s Free plan covers one server plus a database server and 3 apps with no card required; Pro is $19/month per team, Team is $49/month per team for larger setups.
Troubleshooting
Caddy never gets a certificate, or the browser shows a certificate warning. This is almost always DNS, not Caddy. Run dig watch.yourdomain.com +short from your own machine and confirm it returns the server’s IP, not an old one, and not nothing. Let’s Encrypt also needs port 80 reachable from the internet for the initial validation, so check your cloud provider’s firewall or security group allows inbound 80 and 443, not just 443.
docker compose up fails with “port is already allocated.” Something else on the box is already bound to 80 or 443, commonly a default Nginx or Apache install. Find it with sudo ss -tlnp | grep -E ':80|:443' and stop that service, or change which ports Caddy publishes if you genuinely need both running.
A watch shows stale or empty content on a page you know changed. If the page only renders its real content with JavaScript, a basic fetch sees the empty shell the browser would normally fill in. This setup doesn’t include the headless browser component changedetection.io uses for JavaScript-heavy pages; check the project’s own documentation for adding that as a separate container if a specific watch needs it.
A watch keeps reporting a change on a page that, to you, looks identical. Something on the page changes on every load even when the content you care about hasn’t, a timestamp, a rotating ad, a session token embedded in the markup. Narrow the watch to a specific element with the visual selector, or add a filter that strips the noisy part out, rather than diffing the whole page.
FAQ
Does this need a database? No. The single /datastore volume holds the watch configuration, history and snapshots together; there’s no separate database service in this setup.
Can it watch pages that require a login? changedetection.io supports passing cookies or browser session data for watches behind a login wall; check its own documentation for the current way to configure that, since it depends on the specific site and how its session works.
How does it notify me when something changes? changedetection.io ships a range of notification integrations, email, Slack, Discord, generic webhooks and more, configured per watch or globally. Check its own documentation for the current list and setup for each.
How much does this cost to run? The server is $6/month. changedetection.io itself is free, open-source software with no per-watch or per-check fee.
Is Railyard’s template the same architecture as this manual setup? Yes. One container, the same persistent /datastore volume, HTTPS in front of it, no addons. Railyard builds it from the project’s own Dockerfile rather than a pre-built image, but what actually runs is the same thing you’d get by following the steps above.
Either path gets you the same result: a page that used to require you to remember to check it, now checking itself, and telling you the moment it has something worth looking at.
Why people pick Railyard for this
- Your own cloud account. Deploy to DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, or any Linux server over SSH, billed directly by your provider with no markup.
- One flat price per team. Free is free forever: one server plus a database server, three apps, no card. Pro is $19 a month for up to five servers and unlimited apps. Team is $49 a month with unlimited servers. No per-app, per-deploy, per-seat or per-gigabyte charges.
- One-click templates. Deploy open-source apps, like databases, analytics and dev tools, to your own server in one click.
- Managed databases. Postgres, MySQL and Redis with scheduled, restore-verified backups and point-in-time recovery.
- Push-to-deploy. Deploy from GitHub, GitLab or the CLI, with automatic HTTPS and zero-downtime releases.