Deploy your own instance
A watch is only useful if it runs while you are asleep. Watcher separates two planes, and you can host them independently:
| Plane | What it is | Always on? |
|---|---|---|
| Execution | The monitoring itself (pnpm run-due) |
No. A short-lived, outbound-only job. |
| Management | The API + admin panel (pnpm serve) |
No — if it sleeps, watches still run. |
That split is why the whole thing fits in free tiers: the expensive part of hosting is a 24/7 inbound server, and the monitoring does not need one.
Option A — scheduled CI job (recommended)
Section titled “Option A — scheduled CI job (recommended)”The repository ships a scheduled workflow that runs the executor on a cron. It is the simplest way to keep watches running with no server at all.
-
Add the secrets your run needs to the repository’s Actions secrets. At minimum:
Secret Feeds GOOGLE_GENERATIVE_AI_API_KEYthe evaluator, embedder and drafter MONGODB_URIthe database TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_IDalert delivery TAVILY_API_KEY(optional)searchsources and page extraction -
Check the schedule. The shipped workflow runs hourly, in UTC, and also accepts a manual run from the Actions tab with a
forcetoggle that ignores per-watch due-filtering. -
Remember the resolution rule. A watch runs at the slower of its own cron and the workflow’s cron. A watch scheduled
*/5 * * * *under an hourly workflow is checked hourly. See Scheduling.
Option B — always-on service
Section titled “Option B — always-on service”If you want the admin panel, the AI drafter and the REST API, run the service:
pnpm serveIt starts a Fastify API on port 3000 (override with PORT) plus an in-process
scheduler that runs due watches on SCHEDULER_CRON (default */5 * * * *).
There is also a container image:
docker build -t watcher .docker run -d --name watcher --env-file .env -p 3000:3000 watcherGET /health works as a container health check.
Required configuration
Section titled “Required configuration”| Variable | Required |
|---|---|
GOOGLE_GENERATIVE_AI_API_KEY |
yes |
MongoDB_URI |
yes |
telegram_bot_token, telegram_chat_id |
for Telegram alerts |
TAVILY_API_KEY |
for search sources |
| Variable | Required |
|---|---|
| everything the executor needs | yes |
AUTH_JWT_SECRET (32+ characters) |
yes — the API refuses to start without it |
WEB_ORIGIN |
to let a browser panel call it cross-origin |
SCHEDULER_ENABLED=false |
when a CI job already owns the schedule |
The complete list, with defaults and what reads each one, is in Environment variables.
Bootstrap an administrator
Section titled “Bootstrap an administrator”The API requires a signed-in user. Create the first account from the CLI:
ADMIN_PASSWORD='a-long-random-password' pnpm users:create you@example.com --role admin --id localPassing the password through the environment keeps it out of your shell history.
--id local adopts data created before you had accounts, which is what a
single-user instance wants. After that, sign in from the panel or with
POST /auth/login.
Verify the deployment
Section titled “Verify the deployment”GET /healthreturns{"status":"ok"}.GET /config/statusshowstruefor the integrations you configured. It returns booleans only and never a secret value.- Trigger a manual run and confirm a run record appears in the watch’s history.
- Monitoring — how you find out when it breaks.
- Backup and restore — your run state lives in one database, and free tiers do not back it up for you.
- Costs and quotas — what runs out first.