changedetection.io quadlet
A website change monitor with its data in a named volume you can back up in one command.
- Standalone container
- Rootless: systemctl --user
- Tested with podman 5.0 and newer
Last updated
Updated : added a health check that restarts a hung container.
See where this fits on the homelab connections map, with every port it uses.
In short: changedetection.io watches web pages and notifies you when they change. The quadlet is one rootless .container plus a .volume: the volume keeps every watch and snapshot when the container is updated or recreated. Start it with systemctl --user start changedetection.service, open port 5000, and back the data up with podman volume export changedetection -o changedetection.tar.
The quadlet files
Every quadlet file below passes podman's quadlet generator and
systemd-analyze verify on podman 5.0 and the current release. Personal values
are placeholders: replace example.com, /srv/... paths and the
time zone with your own.
changedetection.container
Save as ~/.config/containers/systemd/changedetection.container
[Unit]
Description=changedetection.io website change monitor
[Container]
Image=lscr.io/linuxserver/changedetection.io:latest
ContainerName=changedetection
AutoUpdate=registry
Environment=TZ=Etc/UTC
Volume=changedetection.volume:/config
PublishPort=5000:5000
HealthCmd=curl -fsS -o /dev/null http://127.0.0.1:5000/
HealthInterval=1m
HealthTimeout=10s
HealthRetries=3
HealthStartPeriod=2m
HealthOnFailure=kill
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target
changedetection.volume
Save as ~/.config/containers/systemd/changedetection.volume
[Volume]
VolumeName=changedetection
Why is it built this way?
changedetection.io is a small, stateful service: one container, one folder of data. That makes it the typical shape for most self-hosted apps, one step up from the stateless whoami quadlet.
A named volume for the data. Volume=changedetection.volume:/config
refers to the .volume file, not to a folder. Quadlet turns that file into a small
service that creates the volume, and the container waits for it. The data lives outside the
container, so updates and podman rm leave every watch and snapshot in place.
Backups in one command. podman volume export writes the whole
volume to a tar file, and podman volume import restores it. Run the export from a
systemd timer, and hand the file to restic or borg for an off-site copy.
Rootless with auto-updates. Port 5000 is above 1024, so the service runs under
your user. The linuxserver image only publishes latest and exact build tags;
AutoUpdate=registry with latest follows their stable releases, and
podman auto-update rolls back an image that fails to start.
Health check. The container requests its own start page once a minute. After
three failures in a row, HealthOnFailure=kill stops it and
Restart=always starts it again, so a hung browser worker does not silently stop all
your watches.
Put it behind the Caddy reverse proxy for HTTPS, and
set BASE_URL to that public address so notification links work.
How do I start it?
Run these as your normal user, not as root. Quadlet reads ~/.config/containers/systemd/ for user services.
mkdir -p ~/.config/containers/systemd
# save both files there, then:
systemctl --user daemon-reload
systemctl --user start changedetection.service
systemctl --user enable --now podman-auto-update.timer
loginctl enable-linger "$USER"
# backup of all watches and snapshots:
podman volume export changedetection -o changedetection.tar
Check the result with systemctl --user status and
the logs with journalctl --user -u followed by the
service name. To turn a docker-compose file of your own into files like these, use the
compose to quadlet converter.
Common mistakes
All watches are gone after recreating the container
The data was written inside the container instead of the volume. Check that Volume=changedetection.volume:/config points at /config, the folder the linuxserver image stores its data in, and never remove the volume with podman volume rm to fix a problem.
Links in notifications point to localhost:5000
changedetection.io builds notification links from BASE_URL. Add Environment=BASE_URL=https://changes.example.com with the address you actually open it on, then restart the service.
Port 5000 is already in use
Change only the host side of the mapping, for example PublishPort=5050:5000, then daemon-reload and restart. The container keeps listening on 5000.
Frequently asked questions
Should container data go in a named volume or a bind mount?
Use a named volume (a .volume file) when only the container reads the data: podman creates it with the right ownership, and export and import are one command each. Use a bind mount when you edit the files on the host or share them between containers, like a media library.
How do I back up a podman named volume?
Run podman volume export <name> -o <file>.tar, ideally while the container is stopped, and restore with podman volume import <name> <file>.tar. For regular backups, run the export from a systemd timer and send the file to restic or borg.
Where does rootless podman store named volumes?
Under ~/.local/share/containers/storage/volumes/<name>/_data for the user that runs the container. Rootful volumes live under /var/lib/containers/storage/volumes/.
Can changedetection.io watch pages that need JavaScript?
Yes. According to its documentation, the linuxserver image includes the Playwright content fetcher since version 0.45.16, so you can switch a watch to the browser-based fetch method instead of plain HTTP requests.