behind-the-scenesstartupengineering

From Side Project to Production: The Monitorion Journey

PM

Panos Michalopoulos

Founder & CEO

||8 min read
Share:

Every product starts with a spark of frustration. For Monitorion, that spark was an expired SSL certificate that took down a payment flow for four hours because the monitoring tool I was paying for failed to alert me. What followed was a twelve-month journey from a scrappy side project to a production-grade platform handling thousands of checks per minute across multiple continents.

Choosing the Tech Stack

The first decision was the framework. I chose Next.js with the App Router and React Server Components. The reasoning was pragmatic: server-side rendering for the marketing pages and SEO, server actions for mutations, and a unified codebase for both the dashboard and the API. No separate backend service to deploy and maintain.

For the database and authentication layer, Supabase was a natural fit. PostgreSQL gives us relational integrity for the complex relationships between users, projects, monitors, checks, incidents, and alert channels. Row Level Security (RLS) policies enforce data isolation at the database level, which means a bug in application code cannot leak one customer's data to another. Supabase Auth handles email/password, magic links, and OAuth with minimal custom code.

The job queue was the most critical architectural decision. Monitoring is fundamentally a scheduling problem: thousands of monitors, each with their own interval, need to fire checks reliably and on time. We chose BullMQ backed by Redis. BullMQ gives us delayed jobs, retries with exponential backoff, rate limiting, and — crucially — named workers that can process jobs concurrently across multiple machines.

Architecture Overview

The system has four main components:

  • Next.js application — serves the dashboard, marketing pages, API routes, and server actions. Deployed as a standalone Node.js server behind Nginx.
  • Scheduler worker — runs every 10 seconds, queries for all monitors that are due for a check, and enqueues jobs into BullMQ with the appropriate priority and region routing.
  • Check workers — distributed across multiple regions (currently US-East and AP-Southeast on Fly.io). Each worker pulls jobs from the queue, executes the check, stores results in Supabase, and triggers alerts if the status changed.
  • Aggregation and retention workers — roll up raw check data into hourly and daily summaries, and prune old data based on each customer's subscription tier.

The Multi-Region Challenge

Single-region monitoring is easy. Multi-region monitoring is where things get interesting. When a check from Virginia reports "down" but Singapore reports "up," what do you tell the user? Our answer is consensus-based alerting. A monitor transitions to "down" only when a configurable threshold of regions agree. This virtually eliminates false positives caused by transient network issues at a single checkpoint.

Running workers on Fly.io required solving another problem: connecting to our Redis instance on the primary VPS. We use a Cloudflare tunnel that maps redis-tunnel.monitorion.com to the local Redis port inside each Fly.io container. The worker's entrypoint script starts the tunnel, waits for it to establish, and then launches the Node.js check worker. It is simple, secure, and has been rock-solid in production.

Fighting False Positives

False positives are the single biggest threat to a monitoring platform's credibility. If your tool cries wolf twice, your team starts ignoring alerts — and then misses the real outage. We invested heavily in reducing them:

  • Multi-region consensus as described above.
  • Automatic retries — before marking a check as failed, the worker retries once with a short delay. Transient DNS hiccups and TCP timeouts often resolve on retry.
  • Incident deduplication — a unique partial index in PostgreSQL prevents multiple workers from creating duplicate "open" incidents for the same monitor when checks fire simultaneously from different regions.
  • Configurable thresholds — users can set how many consecutive failures trigger an alert, so a single blip does not page the on-call engineer.

Lessons Learned the Hard Way

Supabase joins have sharp edges. Multi-level joins on self-referencing foreign keys silently return null instead of throwing an error. We learned to split complex queries into separate calls and assemble the data in application code.

Screenshot checks are resource-hungry. Puppeteer with bundled Chromium works, but it needs careful memory management. We cap screenshot size, use a settle delay for JavaScript-heavy pages, and are planning a migration to Supabase Storage to keep the checks table lean.

Redirect checks need millisecond timeouts. An early bug stored the timeout in seconds instead of milliseconds, which caused redirect-chain checks to hang for 30 minutes instead of 30 seconds. Always be explicit about units in configuration objects.

What Is Next

We are expanding to more regions — Frankfurt, Tokyo, and Sao Paulo are next. We are building a public REST API so customers can manage monitors programmatically. And we are working on smarter alerting: anomaly detection based on historical baselines instead of static thresholds. The foundation is solid. Now we scale.

Share:

Enjoyed this post?

Get monitoring tips and product updates delivered to your inbox.


Related Posts