Deutsche Version
Back to overview
| Component |
Use |
| PHP 8.4 and Symfony 8.1 |
Application |
| FrankenPHP (Caddy) |
Web server, in worker mode on live |
| SQLite via Doctrine ORM |
Database and queue |
| Symfony Messenger |
Asynchronous execution of checks and email delivery |
| Symfony Scheduler |
Once-a-minute due check and daily cleanup |
| Twig and Tailwind CSS |
UI |
graph LR
U["Admin in the browser"] -->|"HTTP Basic Auth"| APP["app: UI and API"]
APP --> DB["SQLite: data.db"]
SCH["scheduler"] -->|"every minute"| Q["Queue in SQLite"]
Q --> W["worker"]
W -->|"HTTP, TCP, DNS, Docker"| T["Monitored targets"]
W -->|"SMTP"| M["Mail server"]
W --> DB
AG["agent"] -->|"Bearer token"| APP
| Service |
Command |
Role |
app |
FrankenPHP |
UI and agent API |
worker |
php bin/console messenger:consume async notifications --time-limit=3600 --memory-limit=128M |
Runs dashboard checks and sends emails |
scheduler |
php bin/console messenger:consume scheduler_checks --time-limit=3600 --memory-limit=128M --no-interaction |
Determines every minute which checks are due and cleans up daily |
agent |
php bin/console watchdog:agent:run |
Runs agent checks and reports the results |
mailpit |
axllent/mailpit |
Mail catcher, local and stage only |
All containers except agent and mailpit share the volume with /app/var and thereby the SQLite file. A single worker processes messages one after another. Checks with long time limits therefore delay other checks.
sequenceDiagram
participant S as scheduler
participant Q as queue
participant W as worker
participant D as SQLite
participant M as SMTP
S->>Q: RunSiteChecks per due check
Q->>W: message
W->>D: store result, compare status
W->>Q: MailNotification on status change
Q->>W: message
W->>M: one email per contact
- Every minute the scheduler examines all active dashboard checks of active clients. A check is due when no result exists yet or at least
interval - 30 seconds has passed since the last result. With a daily time it is due once that time of day has been reached and the last result is older than that time.
- For each due check a
RunSiteChecksMessage lands in the async queue.
- The worker runs the check, stores the result and compares it with the previous status (
alert_states).
- On a change it puts a
MailNotificationMessage into the notifications queue, which the same worker processes.
- Agent results enter at step 3 through the API.
| Value |
Duration |
| Due check for dashboard checks |
every minute |
| Cleanup of old results |
daily at 03:00 |
| Agent tick |
30 seconds |
| Agent fetch of the check list |
every 300 seconds |
| Agent counts as online |
contact within the last 10 minutes |
| Retries of failed messages |
3, with doubled delay |
| Limit of container processes |
3600 seconds or 128 MB, then restart by Docker |
| Table |
Content |
clients |
Clients with name and active flag |
contacts |
Contacts with name and unique email address |
client_contacts |
Assignment of client and contact |
client_urls |
URLs per client with label and optional BasicAuth credentials |
site_checks |
Checks: type, configuration (JSON), interval, daily time, retention, runner, agent, active flag, run-now flag |
check_results |
Results: status, HTTP code, response time, message, time |
alert_states |
Current status per check, time of the last status change, counter of consecutive warnings and failures |
agents |
Agents with name, token hash and last contact |
messenger_messages |
Queue and the failed transport |
All paths live under /api/v1/agent and require Authorization: Bearer <token>. Responses are JSON. Without a valid token Watchdog answers 401 with {"error": "Unauthorized"}, and with 429 when the limit of 60 requests per minute and IP is exceeded.
| Method |
Path |
Purpose |
GET |
/config |
The agent's check list. Updates the time of the last contact |
GET |
/run-now |
Checks with the run-now flag set, also for inactive clients. Resets the flag |
POST |
/results |
Accept the results of a tick |
Response of /config:
{
"agent": {"id": 1, "name": "host-server"},
"checks": [
{"id": 7, "type": "disk_space", "config": {"path": "/"}, "check_interval_minutes": 5, "run_at_time": null, "run_now": false}
]
}
Request to /results:
{
"results": [
{"site_check_id": 7, "status": "ok", "message": "42% used (58 GB free of 100 GB)", "response_time_ms": 12, "checked_at": "2026-01-01T12:00:00+01:00"}
]
}
| Response |
Meaning |
200 |
{"accepted": <n>, "skipped": [{"site_check_id": <id>, "reason": "..."}]} |
422 |
Invalid JSON, missing results field or more than 500 results |
401 |
Token missing or invalid |
A result is skipped (skipped) when site_check_id is missing, the check does not exist or is not assigned to this agent, or the status is none of ok, warn, fail, unknown.
| Tool |
Call |
| PHPUnit |
make test |
| PHPStan level 9 |
make stan |
| PHP-CS-Fixer |
make cs (check), make fix (fix) |
| Lint and smoke test |
make lint, make smoke |
CI runs code style, PHPStan and the tests, and a separate workflow builds the agent image.