observer-lite
Lightweight, self-hosted uptime monitoring with HTTP, ping, SSL, heartbeat checks, incidents, maintenance windows, and email/Telegram alerts.
Observer Lite
Lightweight, self-hosted uptime monitoring with email & Telegram alerts,
SSL certificate tracking, real-time dashboard, and a single Docker image.
Overview
Observer Lite is a self-hosted uptime monitoring tool built for simplicity and efficiency. It monitors up to 500+ endpoints simultaneously, tracks SSL certificate expiry, sends instant alerts via email and Telegram, and delivers a real-time dark-mode dashboard — all from a single Docker image with no external database required.
Think of it as a lighter, more flexible alternative to Uptime Kuma — with a built-in status code DSL, response body matching, API keys, audit logging, and configurable data retention.
Full documentation is available in docs/ and can be published with GitHub Pages at:
https://cagatayuresin.github.io/observer-lite/
Features
| Category | Details |
|---|---|
| Monitor Types | HTTP GET / POST / HEAD, Ping (ICMP), SSL Certificate, Heartbeat |
| Status Code Matching | Flexible DSL: 2xx, !5xx, 200|301, 2xx|!503 |
| Body Matching | contains, equals, not_equals against response body |
| SSL Monitoring | Certificate validity + configurable expiry warning (default 30 days) |
| Alerting | Email (SMTP) and Telegram with cooldown, retry logic, and recovery notifications |
| Incidents | Automatic open/close lifecycle, acknowledgement, root cause tracking |
| Maintenance Windows | Suppress alerts during planned downtime, optional cron repeat |
| Real-time Dashboard | Server-Sent Events (SSE) push — no polling required |
| User Management | Roles: superadmin, admin, viewer; forced password change on first login |
| API Keys | obs_-prefixed keys, SHA-256 hashed storage, per-key last-used tracking |
| Audit Log | Immutable record of all user actions |
| Import / Export | JSON-based monitor configuration portability |
| Data Retention | Configurable (default 90 days), auto-cleanup runs nightly |
| UI | Dark mode by default, fully bundled — zero CDN dependencies |
Quick Start
Docker Run
docker run -d \
--name observer-lite \
--restart unless-stopped \
-p 3000:3000 \
-v observer_data:/data \
-e SECRET_KEY="$(openssl rand -hex 32)" \
-e DATABASE_PATH="/data/observer.db" \
-e DATABASE_URL="sqlite+aiosqlite:////data/observer.db" \
ghcr.io/cagatayuresin/observer-lite:latest
Open http://localhost:3000 — log in with admin / admin and set a new password when prompted.
Docker Compose
services:
observer-lite:
image: ghcr.io/cagatayuresin/observer-lite:latest
container_name: observer-lite
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- observer_data:/data
environment:
SECRET_KEY: "change-this-to-a-long-random-secret-key"
DATABASE_PATH: "/data/observer.db"
DATABASE_URL: "sqlite+aiosqlite:////data/observer.db"
PORT: "3000"
volumes:
observer_data:
docker compose up -d
Configuration
All configuration is provided through environment variables.
| Variable | Required | Default | Description |
|---|---|---|---|
SECRET_KEY |
Yes | change-me-… |
HMAC secret for JWT signing and credential encryption. Use a random 32+ byte value. |
DATABASE_PATH |
No | /data/observer.db |
Path to the SQLite database file. |
DATABASE_URL |
No | sqlite+aiosqlite:////data/observer.db |
Full SQLAlchemy async connection URL. |
PORT |
No | 3000 |
Port the application listens on. |
DEBUG |
No | false |
Enable debug logging (true / false). |
Security: Never use the default
SECRET_KEYin production. Generate one withopenssl rand -hex 32.
First Login
- Navigate to
http://your-host:3000 - Log in with username
adminand passwordadmin - You will be immediately redirected to the password change screen
- Set a strong password — the account is then ready to use
Monitor Types
HTTP (GET / POST / HEAD)
Sends an outbound HTTP request and evaluates the response against your rules.
Status Code DSL — combine patterns with
|(OR) and prefix!to negate:Expression Matches 2xxAny 200–299 response 200Exactly 200 2xx|301Any 2xx or exactly 301 !5xxAnything except 5xx 2xx|!503Any 2xx, or anything that isn't 503 Body Matching — optionally assert the response body
contains,equals, ornot_equalsa value.
Ping
Uses the system ping binary to check ICMP reachability. Works with hostnames and IP addresses.
SSL Certificate
Performs a TLS handshake and reports certificate validity and days remaining until expiry. An alert is sent when expiry is within the configured threshold (default: 30 days).
Heartbeat
A passive monitor — your service calls Observer Lite to signal it is alive. If no ping is received within check_interval + grace_seconds, the monitor goes down.
GET /api/heartbeat/{token}
POST /api/heartbeat/{token}
Notifications
Notification channels are configured per-monitor. Each channel can independently toggle alerts for down, recovery, and SSL expiry events.
Email (SMTP)
Configure via the Notification Channels page in the UI. Required fields: SMTP host, port, sender address, recipients list. TLS and STARTTLS are both supported. Passwords are stored with Fernet encryption (AES-128-CBC + HMAC-SHA256).
Telegram
Create a bot via @BotFather, then provide the bot_token and chat_id in the channel settings. A test notification can be sent from the UI before saving.
Architecture
observer-lite/
├── backend/ # Python 3.12 + FastAPI (async)
│ └── app/
│ ├── checkers/ # HTTP, Ping, SSL, Heartbeat probes
│ ├── routers/ # REST API endpoints
│ ├── scheduler/# APScheduler engine + per-monitor jobs
│ ├── services/ # Business logic (incidents, notifications, retention)
│ ├── sse/ # Server-Sent Events broadcaster
│ └── utils/ # Crypto, pagination
└── frontend/ # Vue 3 + Vite + Tailwind CSS + Pinia
Stack highlights:
- Backend: FastAPI, SQLAlchemy 2.0 async, aiosqlite, APScheduler 3.x (SQLAlchemyJobStore), aiosmtplib, python-jose, bcrypt, slowapi
- Frontend: Vue 3, Vite, Tailwind CSS, Pinia, uPlot (lightweight charts, ~40 KB)
- Transport: Server-Sent Events for real-time updates (no WebSocket needed)
- Packaging: Multi-stage Docker build — Node.js build + Python 3.12-slim runtime, single image
Important: The application must run with
--workers 1. The APScheduler and SSE broadcaster are in-memory singletons; multiple workers would duplicate checks and miss events.
API
Interactive API documentation is available at /api/docs (Swagger UI) and /api/redoc (ReDoc) when the server is running.
Key endpoint groups:
POST /api/auth/login | refresh | logout | change-password
GET /api/auth/me
GET|POST|PUT|DELETE /api/monitors/{id}
POST /api/monitors/{id}/pause | resume | check-now
GET /api/monitors/{id}/stats | history | incidents
GET|POST|PUT|DELETE /api/channels + POST /{id}/test
GET|POST|PUT|DELETE /api/incidents + POST /{id}/acknowledge
GET|POST|PUT|DELETE /api/maintenance
GET|POST|DELETE /api/api-keys
GET|PUT /api/settings
GET /api/sse/dashboard (SSE stream)
GET /api/audit-log
GET /api/export/monitors | POST /api/import/monitors
Authentication is via JWT Bearer token or an obs_-prefixed API key.
Development
See CONTRIBUTING.md for the full development guide including local setup, coding standards, and PR process.
# Backend
cd backend
uv venv && uv pip install -e ".[dev]"
alembic upgrade head
uvicorn app.main:app --reload --workers 1
# Frontend (separate terminal)
cd frontend
npm install
npm run dev
# Tests
cd backend
pytest tests/ --cov=app -q
Docker Images
The GitHub Actions workflow publishes the single production image to GHCR. Successful CI runs on main update the moving latest and main tags. Versioned image tags are published only when pushing a matching v* Git tag, with the version read from the root VERSION file.
Moving tags:
ghcr.io/cagatayuresin/observer-lite:latest
ghcr.io/cagatayuresin/observer-lite:main
Release tags include:
ghcr.io/cagatayuresin/observer-lite:0.2.0
ghcr.io/cagatayuresin/observer-lite:v0.2.0
ghcr.io/cagatayuresin/observer-lite:0.2
When pushing a Git tag, use the v-prefixed form that matches VERSION, for example v0.2.0.
Security
Observer Lite is designed with security in mind:
- Passwords hashed with bcrypt (cost 12)
- JWT access tokens expire in 15 minutes; refresh tokens in 30 days
- SMTP/Telegram credentials stored with Fernet symmetric encryption, key derived via PBKDF2
- API key raw values shown once; only SHA-256 hash stored
- Login endpoint rate-limited to 5 attempts / minute per IP via slowapi
Please report vulnerabilities privately — see SECURITY.md for the responsible disclosure process.
License
MIT © 2026 Çağatay Üresin
cagatayuresin