Installing ChangeDetection.io on a VPS: website change monitoring, Telegram notifications, and Docker backups
TL;DR
ChangeDetection.io is a self-hosted service for monitoring changes on websites: prices, product availability, job listings, document text, page statuses, and any other web pages. In this guide, you will deploy it on a VPS using Docker Compose, secure HTTPS with Caddy, configure Telegram notifications, and set up automatic data backups.
- For a small number of checks, a VPS with 1 vCPU, 2 GB RAM, and 25 GB SSD is sufficient.
- ChangeDetection.io stores settings, change history, and page snapshots in a Docker volume.
- Caddy with automatic Let's Encrypt TLS certificates is used for external access.
- Telegram notifications are configured through BotFather and ChangeDetection.io's built-in notification system.
- Backups are performed by a script using Restic to S3-compatible storage or a separate server.
- For JavaScript-heavy websites, you can connect a separate Playwright browser container.
What we are configuring and why
ChangeDetection.io is an open-source web application for tracking website changes. It regularly loads the specified URLs, compares the new version of a page with the previous one, and sends a notification if the content has changed. Unlike simple HTTP monitoring, the service can check text, HTML, a specific CSS selector, a JSON API response, and the visual content of pages.
Common practical use cases include:
- tracking the price of a product, airline ticket, event tickets, or exchange rate;
- monitoring when a product becomes available;
- monitoring new job openings on a company page;
- tracking changes in public offers, policies, pricing plans, and documentation;
- monitoring changes on a competitor's website;
- checking public tenders, procurements, registries, and government portals;
- monitoring the status of a cloud service, CDN, API, or status page;
- receiving a notification when a software product update is released.
After setup, you will have your own ChangeDetection.io instance available through an HTTPS domain. You can create checks through the web interface, choose a run interval, configure text filters, and receive messages in Telegram. Monitoring data remains under your control: history, URLs, page snapshots, and notification tokens are not sent to a third-party SaaS service.
How change detection works
For each monitored page, ChangeDetection.io saves the previous result. During the next check, the service loads the page again, normalizes the content, and calculates the difference. If the differences match the specified rules, a change event is created. You can open its diff in the browser, view added and removed text, and then receive a notification via Telegram, e-mail, Discord, Slack, Matrix, webhook, or other channels supported by Apprise.
Simple HTML pages use a standard HTTP request. This is sufficient for catalogs, documentation, product pages, and most websites without complex client-side rendering. If content appears only after JavaScript execution, you will need the Playwright browser engine. It should only be enabled when necessary: a full browser consumes significantly more memory and CPU than a regular HTTP check.
Cloud-managed services and the self-hosted option
| Criterion | Cloud service | ChangeDetection.io on a VPS |
|---|---|---|
| Getting started | Requires no administration | You need to deploy Docker and configure the server |
| Data control | URLs and change history are stored by the service | Data is stored on your server |
| Check limits | Usually depend on the plan | Limited by VPS resources and website policies |
| Notifications | Available within the plan | Telegram, webhooks, and other integrations without a SaaS subscription |
| Maintenance | Handled by the service provider | You handle updates, backups, and security |
| Flexibility | Limited by the platform interface | You can change the reverse proxy, network, storage, and automation |
A self-hosted installation is especially useful if you need to monitor dozens or hundreds of pages, do not want to share the list of tracked resources with a third party, or require integration with internal systems. However, a VPS does not remove responsibility: you need to update the OS, close unnecessary ports, create backups, and avoid placing excessive load on other websites.
Do not set an excessively short polling interval unless necessary. Checking a public page every 5–15 minutes is usually sufficient. Frequent requests may result in CAPTCHA challenges, IP blocking, or violations of a specific website's rules.
What VPS configuration is needed for this task
ChangeDetection.io load depends not so much on the number of stored URLs as on the check frequency and page type. One hundred simple HTML pages checked once an hour place almost no load on the server. Dozens of pages with JavaScript, authentication, or anti-bot protection may require more CPU and memory, especially if browser rendering is launched simultaneously.
| Scenario | CPU | RAM | Disk | Suitable interval |
|---|---|---|---|---|
| Up to 30 simple HTML pages | 1 vCPU | 1–2 GB | 25 GB SSD | 30–120 minutes |
| 50–200 mixed pages | 2 vCPU | 4 GB | 50–80 GB NVMe | 10–60 minutes |
| JavaScript, Playwright, 200+ checks | 4 vCPU | 8 GB | 100 GB NVMe | 5–30 minutes |
| Thousands of URLs and multiple browser workers | 8+ vCPU | 16+ GB | 200+ GB NVMe | Based on the load profile |
For personal monitoring, a team of several people, or 50–100 uncomplicated pages, a sensible starting point is 2 vCPU, 4 GB RAM, 50 GB NVMe, and an IPv4 address. For example, you can choose a VPS with the specified characteristics, install Ubuntu Server 24.04 LTS, and deploy the entire stack with Docker Compose.
Why spare disk space matters
The service stores change history and snapshots of monitored pages. A single text page takes little space, but large HTML documents, images, lengthy diff history, and a large number of URLs gradually increase the volume size. Do not fill the disk to capacity: Docker and the database need free space for temporary files, logs, and reliable data writes.
A useful rule is to keep at least 20–30% free space. Once a month, check the Docker volume size and remove unused Docker images. Do not perform aggressive volume cleanup: they contain the application's user data.
When you need a dedicated server instead of a VPS
A standard VPS is suitable for nearly all personal and small commercial installations. A dedicated server is justified if you run many browser workers, continuously process thousands of JavaScript pages, need guaranteed CPU without interference from neighbors, or store a large snapshot history. It can also be useful when ChangeDetection.io becomes part of an internal monitoring platform with multiple operators and separate proxies.
Do not switch to a dedicated server just because of a few slow pages. First, increase RAM to 4–8 GB, reduce parallelism, extend check intervals, and verify whether all URLs truly need browser rendering.
How to choose a location
Location affects latency, the geographic version of a website, and the likelihood of anti-bot checks. If you monitor Russian, European, or American websites, choose a server closer to their primary audience. Some stores and services display different prices, currencies, availability, and content depending on the IP address. In that case, the server should be located in the country or region for which you want to receive data.
For the management panel, location is less critical: the interface is lightweight and works over HTTPS. Network stability, available IPv4, sufficient traffic, and the ability to configure a DNS record for the domain are more important.
Server preparation
Ubuntu Server 24.04 LTS is used below. At the time this guide was prepared, it is a stable LTS release suitable for Docker hosts. The commands are also similar for Debian 12, but the names of some packages and firewall settings may differ.
It is assumed that the server already has a public IPv4 address and that an A record has been created in the DNS panel, for example changes.example.com, pointing to this address. Without a correct DNS record, Caddy will not be able to automatically issue a Let's Encrypt certificate.
Initial login and system update
Log in as the root user only for the initial setup. Update the system immediately, create a regular user, and transfer your SSH key to it. Going forward, do not operate the ChangeDetection.io panel or Docker as root without a reason.
ssh root@SERVER_IP
apt update && apt upgrade -y
apt install -y ca-certificates curl gnupg ufw fail2ban vim jq unzip
reboot
After rebooting, connect again and create a user. This example uses the name deploy; replace it with your own.
adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
Copy your public key from the local computer. Run this command on the local machine, not on the server.
ssh-copy-id deploy@SERVER_IP
Verify login in a new session before disabling root access.
ssh deploy@SERVER_IP
sudo whoami
The expected result of the last command is root. This means the user can perform administrative actions through sudo.
Securing SSH
Open a separate SSH session and do not close the old one until you confirm that key-based login works. Then create a separate SSH configuration file. This approach is safer than editing the main package file.
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf > /dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
EOF
sudo sshd -t
sudo systemctl restart ssh
The sshd -t command checks syntax before restarting the service. If the test completes without errors, the configuration is correct. Root login and password authentication are now disabled, and connections are possible only with an SSH key.
Firewall and Fail2ban
ChangeDetection.io should not directly listen on a public port. Only SSH, HTTP for issuing the TLS certificate, and HTTPS for the panel will be available externally. The application Docker port will remain available only on 127.0.0.1.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Verify that only ports 22, 80, and 443 are allowed in the output. If SSH runs on a non-standard port, allow that port first, otherwise you may lose access to the server.
Fail2ban blocks IP addresses after a series of failed login attempts. On Ubuntu, simply enable the service and add a basic local configuration.
sudo tee /etc/fail2ban/jail.d/sshd.local > /dev/null <<'EOF'
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Basic host checks
Before installing Docker, make sure the time is synchronized, there is enough free disk space, and the DNS domain already points to the server. Incorrect time causes problems with TLS certificates and logs.
timedatectl status
df -h /
free -h
getent hosts changes.example.com
Replace changes.example.com with your domain. The result of the last command should display the public IP address of your VPS.
Software Installation — Step by Step
Docker Engine 28.x and Docker Compose Plugin v2 are used for isolated deployment. Docker allows you to update the application with a single command, avoid mixing dependencies with the OS, and conveniently store data in a separate volume. Application images will be downloaded from GitHub Container Registry.
Installing Docker Engine from the Official Repository
Remove old Docker packages if they were installed from the standard Ubuntu repository. The command is safe on a new server.
sudo apt remove -y docker.io docker-compose docker-compose-v2 podman-docker containerd runc || true
Add the Docker key and repository. The official Docker repository for Ubuntu 24.04 is used.
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
Install Docker Engine, Buildx, and Compose Plugin. In 2026, Compose is recommended for use as the docker compose subcommand rather than the legacy standalone docker-compose utility.
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version
Allow the deploy user to work with Docker without constantly using sudo. You must log in again afterwards.
sudo usermod -aG docker deploy
exit
Reconnect and verify access.
ssh deploy@SERVER_IP
docker run --rm hello-world
Creating the Directory Structure
Do not store project files in the root home directory. We will create the /opt/changedetection directory, accessible to the deploy user. It will contain the Compose configuration, environment variables, Caddyfile, and a local backup directory.
sudo mkdir -p /opt/changedetection/{caddy,data,backups,scripts}
sudo chown -R deploy:deploy /opt/changedetection
cd /opt/changedetection
pwd
Creating the Environment Variables File
The .env file must not be committed to a public Git repository. It defines the domain, time zone, and base URL. It is not necessary to add Telegram secrets to this file: they can be saved in the application interface, and the tokens will be stored in its data. If you automate the configuration, store tokens only in a private file with 600 permissions.
cd /opt/changedetection
cat > .env <<'EOF'
CD_DOMAIN=changes.example.com
TZ=Europe/Moscow
BASE_URL=https://changes.example.com
EOF
chmod 600 .env
Change the domain and time zone to your own values. You can view the list of time zones with timedatectl list-timezones.
Creating the Docker Compose Configuration
The main ChangeDetection.io container will listen only on the local address 127.0.0.1:5000. Caddy will access it through the internal Docker network. This restriction prevents bypassing the HTTPS proxy through direct external access to port 5000.
cat > docker-compose.yml <<'EOF'
services:
changedetection:
image: ghcr.io/dgtlmoon/changedetection.io:latest
container_name: changedetection
restart: unless-stopped
environment:
- TZ=${TZ}
- BASE_URL=${BASE_URL}
- USE_X_SETTINGS=1
volumes:
- ./data:/datastore
expose:
- "5000"
networks:
- monitor_net
caddy:
image: caddy:2.10-alpine
container_name: changedetection-caddy
restart: unless-stopped
env_file:
- .env
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy/data:/data
- ./caddy/config:/config
networks:
- monitor_net
networks:
monitor_net:
driver: bridge
EOF
The latest tag is convenient for the first launch, but for a critical installation it is better to pin a specific release or image digest after successful testing. This makes updates reproducible and reduces the risk of unexpected behavior changes on the next pull.
First Container Startup
At this stage, the Caddyfile has not yet been created, so prepare it in the next section first. After creating the files, start the stack. Docker will download the images and create the network and volume directories.
cd /opt/changedetection
docker compose config
docker compose pull
docker compose up -d
docker compose ps
The docker compose config command checks syntax and variable substitution. In docker compose ps, both containers should have the running status.
Configuration
Configuring HTTPS with Caddy
Caddy automatically obtains and renews Let's Encrypt TLS certificates. For this to work, the domain must be accessible from the internet on ports 80 and 443. You do not need to specify certificate paths manually in the Caddyfile.
cd /opt/changedetection
cat > caddy/Caddyfile <<'EOF'
{
email [email protected]
}
{$CD_DOMAIN} {
encode zstd gzip
reverse_proxy changedetection:5000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
log {
output stdout
format json
}
}
EOF
Replace [email protected] with a valid address for certificate issue notifications. Then restart the stack.
cd /opt/changedetection
docker compose up -d
docker compose logs -f caddy
In the Caddy logs, look for a message confirming that the certificate was obtained successfully. To exit log viewing, press Ctrl+C; the container will continue running. Then open https://changes.example.com in your browser.
Health Check
Check the server response from the VPS itself and from your local computer. The domain check should return HTTP status 200 or a redirect to the application page.
curl -I http://127.0.0.1:5000
curl -I https://changes.example.com
docker compose ps
docker compose logs --tail=100 changedetection
If the first command does not work, the problem is inside the ChangeDetection.io container. If the local check succeeds but the domain does not open, check DNS, the firewall, ports 80/443, and Caddy logs.
Initial Setup in the Web Interface
Open the domain in a browser. In the current version of ChangeDetection.io, you will see a form for adding a new URL. For the first test, use a stable public page, such as a project documentation page or a service status page. Do not start testing with a site that actively uses CAPTCHA and anti-bot protection.
- Click to add a new watch.
- Enter the page URL.
- Select a check interval: 30–60 minutes is sufficient for testing.
- Save the watch.
- Run a manual check using the check button.
- Open the history and confirm that the first snapshot has been created.
The first page save is usually not considered a change because the service needs to create a baseline version for comparison. A notification will appear after the next differing result.
Filtering by CSS Selector and Text
Monitoring an entire page often creates unnecessary notifications: banners, publication dates, counters, advertisements, and recommendation blocks change. It is better to extract only the meaningful part of the page. To do this, use a CSS selector in the watch settings.
For example, for a product price, the selector may look like .product-price, [data-testid="price"], or span.price. You can find it using browser developer tools: open the page, press F12, select the required element, and inspect its classes, attributes, and HTML structure.
After adding the selector, run a check manually. If only the expected fragment is displayed in the history, the configuration works. If the result is empty, the selector is incorrect or the required element is created by JavaScript after the page loads.
Connecting a JavaScript Browser When Needed
Not all sites return final content through a regular HTTP request. Online stores, SPA interfaces, and modern personal accounts often render data in the browser. For such URLs, connect a separate Playwright-based browser container. Do this only after verification: browser rendering is significantly more resource-intensive.
Add a browser service to docker-compose.yml and the PLAYWRIGHT_DRIVER_URL variable to the changedetection service. The specific supported image should be verified against the current ChangeDetection.io documentation before updating, since browser driver compatibility depends on the application version.
changedetection:
environment:
- TZ=${TZ}
- BASE_URL=${BASE_URL}
- USE_X_SETTINGS=1
- PLAYWRIGHT_DRIVER_URL=ws://playwright:3000
playwright:
image: browserless/chromium:latest
container_name: changedetection-playwright
restart: unless-stopped
environment:
- TOKEN=change-this-browser-token
- MAX_CONCURRENT_SESSIONS=2
- CONNECTION_TIMEOUT=60000
networks:
- monitor_net
For a production configuration, do not leave latest or the sample token in place. Pin a tested image version, set a random token through .env, and limit the number of concurrent sessions according to the server's RAM. On a VPS with 2 GB of memory, start with one browser worker.
Setting Up Telegram Notifications
Telegram is a convenient channel for immediate notifications. Create a bot through the official @BotFather account. Send the /newbot command, set a name and username, and save the issued API token. This token grants access to send messages on behalf of the bot, so do not publish it in screenshots, Git repositories, or shell history.
Then open a private chat with the created bot and send it the /start command. For a group, add the bot to the group, send a message, and grant posting permissions if necessary. To find out the chat_id, you can temporarily open the Telegram API URL after sending a message to the bot:
curl "https://api.telegram.org/botВАШ_ТОКЕН/getUpdates"
In the JSON response, find the chat field, then the id value. For group chats, the ID often starts with a minus sign. After obtaining the ID, remove the command from terminal history or use a temporary shell variable so the token is not stored in history.
export TG_TOKEN='ВАШ_ТОКЕН'
curl "https://api.telegram.org/bot${TG_TOKEN}/getUpdates"
unset TG_TOKEN
In ChangeDetection.io, open the notification settings. In the notification URL field, add the Telegram Apprise URL in the following format:
tgram://BOT_TOKEN/CHAT_ID
Substitute the token and chat ID. If the interface provides a test send button, use it before attaching the notification to dozens of pages. Under normal circumstances, the bot will immediately send a test message. For individual watches, you can override global notifications and route different site categories to different Telegram groups.
Basic Web Interface Protection
ChangeDetection.io contains a list of tracked sites and change history, so do not leave the interface open to the entire internet unless it needs to be public. The minimum option is to use the application's built-in authentication if it is available in the current release. Alternatively, add Basic Auth at the Caddy level.
docker run --rm caddy:2.10-alpine caddy hash-password --plaintext 'СЛОЖНЫЙ_ПАРОЛЬ'
Copy the resulting hash and add it to the Caddyfile site block before reverse_proxy.
basic_auth {
monitor $2a$14$REPLACE_WITH_GENERATED_BCRYPT_HASH
}
After modifying the Caddyfile, apply the configuration by restarting the container.
cd /opt/changedetection
docker compose restart caddy
docker compose logs --tail=50 caddy
Basic Auth creates an additional barrier before accessing the application. Use a long, unique password and do not pass it in a URL. If necessary, restrict access by IP through a firewall or VPN, especially if this is an internal work tool.
Backups and Maintenance
Docker containers can be recreated at any time, but ChangeDetection.io data cannot be restored without a backup. In the current setup, the critical directory is /opt/changedetection/data. It contains the database, watch settings, check history, screenshots, and other application data. You should also save docker-compose.yml, .env, the Caddyfile, and the Caddy directory with certificates.
What to Include in the Backup
| Path | What is stored | Criticality |
|---|---|---|
/opt/changedetection/data |
Database, URLs, settings, history, snapshots | Critical |
/opt/changedetection/docker-compose.yml |
Service definitions | Critical |
/opt/changedetection/.env |
Domain, time zone, secrets if present | Critical |
/opt/changedetection/caddy/Caddyfile |
Reverse proxy and panel protection | Important |
/opt/changedetection/caddy/data |
TLS certificates and Caddy state | Recommended |
Do not consider copying data to the same disk a backup. If the VPS is deleted, the file system is damaged, or the account is blocked, a local archive will not help. Keep backups in at least one external location: an S3-compatible bucket, a separate server via SFTP/SSH, or specialized backup storage.
Installing Restic
Restic creates encrypted, deduplicated backups. It is well suited for small servers: only new data blocks are transferred, and access to the storage is protected by a repository password. The example below uses S3-compatible storage.
sudo apt update
sudo apt install -y restic
restic version
Create a protected file with the parameters. The example uses variables for S3. Substitute the endpoint, bucket name, keys, and a long unique password. Do not use your SSH or hosting panel password.
sudo tee /etc/changedetection-backup.env > /dev/null <<'EOF'
export RESTIC_REPOSITORY="s3:https://s3.example.com/changedetection-backups"
export AWS_ACCESS_KEY_ID="S3_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="S3_SECRET_KEY"
export RESTIC_PASSWORD="LONG_UNIQUE_RESTIC_PASSWORD"
EOF
sudo chmod 600 /etc/changedetection-backup.env
sudo chown root:root /etc/changedetection-backup.env
Initialize the repository. This operation is performed once.
sudo bash -c 'source /etc/changedetection-backup.env && restic init'
Automatic Backup Script
For consistency, it is better to stop the application container briefly before copying its database, and then start it again. With a small amount of data, the downtime usually lasts seconds. Caddy will continue responding, but the application will be temporarily unavailable during this time.
sudo tee /opt/changedetection/scripts/backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="/opt/changedetection"
LOG_FILE="/var/log/changedetection-backup.log"
source /etc/changedetection-backup.env
echo "[$(date -Is)] Backup started" >> "$LOG_FILE"
cd "$PROJECT_DIR"
docker compose stop changedetection
trap 'docker compose start changedetection' EXIT
restic backup \
"$PROJECT_DIR/data" \
"$PROJECT_DIR/docker-compose.yml" \
"$PROJECT_DIR/.env" \
"$PROJECT_DIR/caddy/Caddyfile" \
"$PROJECT_DIR/caddy/data" \
--tag changedetection \
--tag "$(hostname)"
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune
echo "[$(date -Is)] Backup completed" >> "$LOG_FILE"
EOF
sudo chmod 700 /opt/changedetection/scripts/backup.sh
sudo chown root:root /opt/changedetection/scripts/backup.sh
The script retains seven daily, four weekly, and six monthly restore points. This policy is suitable for most small installations. If the change history is especially valuable, increase the monthly retention period or create a monthly copy in a separate bucket.
Run the backup manually and make sure the container is running again.
sudo /opt/changedetection/scripts/backup.sh
sudo bash -c 'source /etc/changedetection-backup.env && restic snapshots'
cd /opt/changedetection && docker compose ps
Cron Scheduler
Run the backup at night, for example daily at 03:20. Open the root user's crontab.
sudo crontab -e
Add the following line:
20 3 * /opt/changedetection/scripts/backup.sh
Check the logs after the first night. Cron usually runs with a minimal environment, so the script explicitly loads the file containing the S3 variables.
sudo tail -n 100 /var/log/changedetection-backup.log
sudo grep CRON /var/log/syslog | tail -n 20
Testing Restoration
A backup without a restoration test is only an assumption. Every few months, restore a copy to a separate temporary directory or test VPS. Do not restore over production data until you have verified it.
sudo mkdir -p /tmp/changedetection-restore-test
sudo bash -c 'source /etc/changedetection-backup.env && restic restore latest --target /tmp/changedetection-restore-test'
sudo find /tmp/changedetection-restore-test -maxdepth 3 -type f | head
After testing, delete the temporary directory. For complete disaster recovery, install Docker on a new server, copy the Compose files, restore the directory to /opt/changedetection, configure DNS, and run docker compose up -d.
Updating ChangeDetection.io and Docker Images
For a single ChangeDetection.io instance, a short maintenance window is usually sufficient. Before updating, perform a backup, review the project's release notes, and only then download the new image. Do not update the application automatically when you cannot check the result.
cd /opt/changedetection
sudo /opt/changedetection/scripts/backup.sh
docker compose pull
docker compose up -d
docker image prune -f
docker compose ps
docker compose logs --tail=100 changedetection
With a single replica, you cannot perform a full rolling update without a brief interruption: the container is recreated. This is fine for home and team monitoring. A highly available setup would require a separate database, shared storage, a load balancer, and compatibility testing, which is usually excessive for this task.
Update the OS regularly, for example once a week or month. After a kernel update, reboot the server during a scheduled maintenance window.
sudo apt update
sudo apt upgrade -y
sudo reboot
Troubleshooting and FAQ
Why does the domain open over HTTP, but an HTTPS certificate is not issued?
First, check that the domain's A record points to the VPS IP: getent hosts ваш-домен. Then make sure ports 80 and 443 are allowed in UFW and in the provider panel's network firewall. Check docker compose logs caddy: it usually states the exact reason, such as incorrect DNS, a closed port, or too many certificate issuance attempts. Also make sure another web server is not occupying ports 80 and 443.
The ChangeDetection.io container keeps restarting. What should I check?
Run docker compose logs --tail=200 changedetection and check for access errors involving the data directory, a corrupted database, or insufficient disk space. The df -h command shows disk usage, while free -h shows available memory. If the process was terminated by the kernel due to insufficient RAM, dmesg -T may contain OOM entries. Increase the VPS memory, disable the browser container, or reduce the number of concurrent checks.
Why are Telegram notifications not arriving?
Make sure the user or group first sent the bot a message or the /start command: a bot cannot initiate a conversation with a user. Verify the token and chat_id, especially for groups, where the ID is often negative. Test the notification using the built-in test button in the interface. If the test does not work, request getUpdates from the Telegram API again and make sure the setting contains a valid Apprise URL in the format tgram://BOT_TOKEN/CHAT_ID.
The site shows an empty result or does not detect a price that is visible in the browser. How can I fix this?
First, open the saved snapshot in ChangeDetection.io. If the required block is missing, the site probably generates content with JavaScript, requires cookies or geolocation, or blocks the server IP. Try configuring a CSS selector for the static portion, changing the user-agent in the watch settings, or connecting a Playwright browser. Do not attempt to bypass CAPTCHA or site protection mechanisms without the resource owner's permission.
What is the minimum suitable VPS configuration?
The minimum for 10–30 standard HTML checks is 1 vCPU, 1–2 GB of RAM, and a 25 GB SSD. It is more practical to start with 2 vCPUs, 4 GB of RAM, and a 50 GB NVMe drive, leaving room for Docker, change history, a TLS proxy, and several complex pages. If Chromium or Playwright is enabled for JavaScript rendering, 2 GB of memory is often insufficient. For browser-based checks, a reasonable minimum is 4 GB of RAM.
Which should I choose — a VPS or a dedicated server for this task?
For most installations, choose a VPS. It is cheaper, scales faster, and easily handles dozens or hundreds of standard URLs. A dedicated server makes sense for thousands of frequent checks, many concurrent browser sessions, a large snapshot archive, or guaranteed CPU performance requirements. Before switching to a dedicated server, measure actual resource usage with docker stats rather than relying only on the number of URLs.
After the update, my settings or watch history disappeared. Why?
The most common cause is starting the container without the previous ./data:/datastore mount or running Compose from a different directory. Check the path in docker-compose.yml, the current project directory, and the presence of files in /opt/changedetection/data. Do not delete volumes or the data directory when cleaning up Docker. If the data was actually deleted, stop the application and restore the latest Restic backup to the original path.
Why did the server receive a block or CAPTCHA from the monitored site?
Most likely, requests are being made too frequently, with too much concurrency, or the site prohibits automated access. Increase the check interval, reduce the number of concurrent tasks, disable rapid retries, and review the site's rules. Use the official API, RSS feed, or the site's e-mail notifications if available. Monitoring should be considerate: the technical ability to load a page does not always mean that mass data collection is permitted.
Conclusions and Next Steps
ChangeDetection.io now runs on your VPS in Docker, is available over HTTPS, sends changes to Telegram, and is protected by external backups. This setup is suitable for personal monitoring, work notifications, and small internal services.
- Add the first 5–10 watches and configure a precise CSS selector for each to eliminate false changes.
- Check the size of the
datadirectory after a month and adjust the disk capacity or backup retention period. - If JavaScript-driven sites appear, test the Playwright container separately and increase RAM before enabling browser rendering at scale.