changedetection.io: Self-Hosted Website Change Monitoring with Multi-Protocol Fetching
┌─────────────────────────────────────────────────────┐
│ Analysis Summary │
├─────────────────────────────────────────────────────┤
│ Type: Project │
│ Primary Language: python + javascript + yaml │
│ LOC: 37K │
│ Test Files: 101 │
│ Architecture: Flask │
│ Confidence: High │
└─────────────────────────────────────────────────────┘
Analyzed: 2fb2ea57 from 2025-10-16
changedetection.io: Self-Hosted Website Change Monitoring with Multi-Protocol Fetching
changedetection.io is a self-hosted web application for monitoring website changes and sending notifications when content updates are detected. The project provides a Flask-based web interface with support for multiple content fetching methods, including basic HTTP requests, Chrome/Playwright browser automation, and specialized parsers for JSON APIs and PDF files.
The codebase analysis reveals a mature Python application with 37,041 lines of code across 247 files, featuring comprehensive test coverage with 101 test files and extensive dependency management through 61 packages including Flask, BeautifulSoup, Playwright, and the Apprise notification library.
Quick Start
$ docker compose up -d
Alternative Docker standalone:
$ docker run -d --restart always -p "127.0.0.1:5000:5000" -v datastore-volume:/datastore --name changedetection.io dgtlmoon/changedetection.io
Time to first result: ~2 minutes
Alternative Approaches
| Solution | Setup Time | Resource Use | Cost | Platform Support |
|---|---|---|---|---|
| changedetection.io | Low (Docker) | Medium | Free/Self-hosted | Cross-platform |
| Visualping | Instant | None | $14/month | Cloud only |
| Distill Web Monitor | Medium | Low | $9/month | Browser extension |
| Uptimerobot | Low | None | $7/month | Cloud only |
| Wachete | Low | None | $6/month | Cloud only |
Architecture and Implementation
The application follows a modular Flask architecture with async worker management for scalable monitoring operations. The core data model extends Python’s built-in dictionary class with configuration management:
class model(dict):
base_config = {
'note': "Hello! If you change this file manually, please be sure to restart your changedetection.io instance!",
'watching': {},
'settings': {
'headers': {
},
'requests': {
'extra_proxies': [], # Configurable extra proxies via the UI
'extra_browsers': [], # Configurable extra proxies via the UI
'jitter_seconds': 0,
'proxy': None, # Preferred proxy connection
'time_between_check': {'weeks': None, 'days': None, 'hours': 3, 'minutes': None, 'seconds': None},
'timeout': int(getenv("DEFAULT_SETTINGS_REQUESTS_TIMEOUT", "45")), # Default 45 seconds
'workers': int(getenv("DEFAULT_SETTINGS_REQUESTS_WORKERS", "10")), # Number of threads, lower is better for slow connections
The worker management system (changedetectionio/worker_handler.py:26-40) implements async event loops for concurrent monitoring:
def start_async_event_loop():
"""Start a dedicated event loop for async workers in a separate thread"""
global async_loop
logger.info("Starting async event loop for workers")
try:
# Create a new event loop for this thread
async_loop = asyncio.new_event_loop()
# Set it as the event loop for this thread
asyncio.set_event_loop(async_loop)
logger.debug(f"Event loop created and set: {async_loop}")
# Run the event loop forever
async_loop.run_forever()
Worker creation uses factory functions to generate named coroutines for better debugging and monitoring (changedetectionio/worker_handler.py:80-94):
def create_named_worker(worker_id):
async def named_worker():
task = asyncio.current_task()
if task:
task.set_name(f"async-worker-{worker_id}")
return await start_single_async_worker(worker_id, update_q, notification_q, app, datastore)
return named_worker()
task_future = asyncio.run_coroutine_threadsafe(create_named_worker(i), async_loop)
running_async_tasks.append(task_future)
The configuration system includes header parsing utilities for custom HTTP headers (changedetectionio/model/App.py:80-89):
def parse_headers_from_text_file(filepath):
headers = {}
with open(filepath, 'r') as f:
for l in f.readlines():
l = l.strip()
if not l.startswith('#') and ':' in l:
(k, v) = l.split(':', 1) # Split only on the first colon
headers[k.strip()] = v.strip()
return headers
Performance Characteristics
Bundle Impact:
- Core application: Flask-based web server with minimal static assets
- JavaScript components: 2,865 lines for UI interactions
- Python codebase: 30,782 lines of monitoring logic
- Total Docker image: Includes Chrome browser and dependencies
Other Considerations:
- Runtime dependencies: 61 packages including browser automation tools
- Test coverage: 101 test files across the codebase
- Build tooling: Docker containerization with multi-stage builds
Best for: Organizations requiring self-hosted monitoring with custom notification workflows and browser automation capabilities.
Security Architecture
Credential storage: Environment variable configuration with Docker volume persistence for sensitive data like proxy credentials and API keys.
Authentication: Flask-Login integration for session management with configurable authentication backends.
Network security: Configurable proxy support with rate limiting through worker thread management. CORS enabled for Chrome extension integration.
Audit logging: Structured logging throughout the application with configurable log levels and worker identification.
Transport: HTTPS support through reverse proxy configuration, with secure header handling for authenticated requests.
When to Use changedetection.io
The evidence suggests this project fits well for:
- Organizations requiring self-hosted monitoring solutions with full data control and custom notification integrations
- Complex monitoring scenarios requiring browser automation, JavaScript execution, and visual element selection
- API monitoring workflows where JSON parsing, XPath filtering, and conditional triggers are essential
Consider alternatives when:
- Simple monitoring needs where cloud-based solutions provide adequate functionality without infrastructure overhead
- Resource-constrained environments where the Docker deployment and Chrome browser requirements exceed available capacity
- Teams requiring enterprise support contracts and guaranteed SLA commitments that self-hosted solutions cannot provide