Cloudsteading
Atalaya’s documented public example showing 90-day uptime and response-time charts for ifconfig.es services.

Atalaya

Monitor endpoints from Cloudflare regions and publish an uptime status page.

Atalaya is a self-hosted Better Stack/Pingdom alternative built on Cloudflare (D1, Durable Objects, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.

Source & license

Upstream license: Apache-2.0

License TL;DR

You can use, change and sell it, including in closed-source products. When sharing copies, include the license, keep required notices and mark changed files. It includes a contributor patent grant with conditions, but no trademark permission or warranty.

Explain Apache 2.0 in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@dcarrillo

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Free tier eligible within limits

The documented Atalaya deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs.

Hosting requirements
  • Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits.
  • Keep aggregate D1 use below 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; a request can touch many rows.
  • Use the configured SQLite Durable Object classes within 100,000 requests/day, 13,000 GB-s duration/day, 5 million SQL rows read/day, 100,000 written/day and 5 GB storage; active sockets consume duration.
  • Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: 5437d1c. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • uptimerobot ↗

    Atalaya (Spanish for watchtower) is an uptime & status page monitoring service running on Cloudflare Workers and Durable Objects allowing checks from multiple regions.

  • better-stack ↗

    Atalaya (Spanish for watchtower) is an uptime & status page monitoring service running on Cloudflare Workers and Durable Objects allowing checks from multiple regions.

  • pingdom ↗

    Atalaya (Spanish for watchtower) is an uptime & status page monitoring service running on Cloudflare Workers and Durable Objects allowing checks from multiple regions.

  • workers ↗

    name = "atalaya" main = "src/index.ts" compatibility_date = "2026-07-06" compatibility_flags = ["nodejs_compat"] workers_dev = false [cache] enabled = true [assets] directory = "./status-page/dist/client/" binding = "ASSETS" run_worker_first = true [triggers] crons = ["* * * * *", "0 * * * *"] [[d1_databases]] binding = "DB" database_name = "atalaya" database_id = "xxxxxxxx-yyyy-xxxx-iiii-jjjjjjjjjjjj" migrations_dir = "./migrations" [[durable_objects.bindings]] name = "REGIONAL_CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Mo

  • d1 ↗

    worker_first = true [triggers] crons = ["* * * * *", "0 * * * *"] [[d1_databases]] binding = "DB" database_name = "atalaya" database_id = "xxxxxxxx-yyyy-xxxx-iiii-jjjjjjjjjjjj" migrations_dir = "./migrations" [[durable_objects.bindings]] name = "REGIONAL_CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Monitor" default_retries: 2 default_retry_delay_ms: 1000 default_timeout_ms: 5000 default_failure_threshold: 2 alerts: - name: "telegram" type: webhook url: "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" method: POST headers: Content-Ty

  • durable-objects ↗

    CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Monitor" default_retries: 2 default_retry_delay_ms: 1000 default_timeout_ms: 5000 default_failure_threshold: 2 alerts: - name: "telegram" type: webhook url: "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" method: POST headers: Content-Type: application/json body_template: | { "chat_id": "${TELEGRAM_CHAT_ID}", "text": "{{monitor.name}} transitioned from status {{status.previous}} to status {{status.current}} {{check.error}}" } - name: "default" type:

  • free-tier-eligible ↗

    name = "atalaya" main = "src/index.ts" compatibility_date = "2026-07-06" compatibility_flags = ["nodejs_compat"] workers_dev = false [cache] enabled = true [assets] directory = "./status-page/dist/client/" binding = "ASSETS" run_worker_first = true [triggers] crons = ["* * * * *", "0 * * * *"] [[d1_databases]] binding = "DB" database_name = "atalaya" database_id = "xxxxxxxx-yyyy-xxxx-iiii-jjjjjjjjjjjj" migrations_dir = "./migrations" [[durable_objects.bindings]] name = "REGIONAL_CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Mo

  • free-tier-eligible ↗

    worker_first = true [triggers] crons = ["* * * * *", "0 * * * *"] [[d1_databases]] binding = "DB" database_name = "atalaya" database_id = "xxxxxxxx-yyyy-xxxx-iiii-jjjjjjjjjjjj" migrations_dir = "./migrations" [[durable_objects.bindings]] name = "REGIONAL_CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Monitor" default_retries: 2 default_retry_delay_ms: 1000 default_timeout_ms: 5000 default_failure_threshold: 2 alerts: - name: "telegram" type: webhook url: "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" method: POST headers: Content-Ty

  • free-tier-eligible ↗

    CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Monitor" default_retries: 2 default_retry_delay_ms: 1000 default_timeout_ms: 5000 default_failure_threshold: 2 alerts: - name: "telegram" type: webhook url: "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" method: POST headers: Content-Type: application/json body_template: | { "chat_id": "${TELEGRAM_CHAT_ID}", "text": "{{monitor.name}} transitioned from status {{status.previous}} to status {{status.current}} {{check.error}}" } - name: "default" type:

  • free-tier-eligible ↗

    up>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 seconds)<br> Max of 15 minutes of CPU time per [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) or [Queue Consumer](https://developers.cloudflare.co

  • free-tier-eligible ↗

    oudflare.com/workers/platform/pricing/#workers) | | --- | --- | --- | | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows | | Storage (per GB stored) | 5 GB (total) | First 5 GB included + $0.75 / GB-mo | Track your D1 usage To accurately track your usage, use the [meta object](https://developers.cloudflare.com/d1/worker-api/return-object/), [GraphQL Analytics API](https://developers.cloudflare.com/d1/observability/metrics-analytics/#query-via-the-graphql-api), or the [Cloudflare dashboard ↗︎](https://dash.cloudflare.com/?to=/:account/workers/d1/). Select your D1 database, then vie

  • free-tier-eligible ↗

    jects are available both on Workers Free and Workers Paid plans. - **Workers Free plan**: Only Durable Objects with [SQLite storage backend](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) are available. - **Workers Paid plan**: Durable Objects with the SQLite storage backend are available. The [key-value storage backend](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is only available to accounts that already have a key-value-backed namespace. If you wish to downgrade from a Workers Paid plan to a Workers Free plan, you must first ensure that you have deleted all Durable Object namespaces with the key-value storage backend. On Workers Free plan: - If you exceed any one of the free tier limits, further operations of that type will fail with an error. - Daily free limits reset at 00:00 UTC. ## Compute billing Durable Objects are billed for compute duration (wall-clock time) while the Durable Object is actively running or is idle in memory but unable to [hibernate](https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/). Durable Objects that are idle and eligible for hibernation are not billed for duration, even before the runtime has hibernated them. Requests to a D

  • free-tier-eligible ↗

    billed accordingly. | | Free plan | Paid plan | | --- | --- | --- | | Requests | 100,000 / day | 1 million / month, + $0.15/million<br> Includes HTTP requests, RPC sessions<sup>1</sup>, WebSocket messages<sup>2</sup>, and alarm invocations | | Duration<sup>3</sup> | 13,000 GB-s / day | 400,000 GB-s / month, + $12.50/million GB-s<sup>4,5</sup> | <details> <summary> Footnotes </summary> <sup>1</sup> Each <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/">RPC session</a> is billed as one request to your Durable Object. Every <a href="https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/">RPC method call</a> on a <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects stub</a> is its own RPC session and therefore a single billed request. RPC method calls can return objects (stubs) extending <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/#lifetimes-memory-and-resource-management"><code>RpcTarget</code></a> and invo

  • free-tier-eligible ↗

    /). | | Workers Free plan | Workers Paid plan | | --- | --- | --- | | Rows reads <sup>1,2</sup> | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written <sup>1,2,3,4</sup> | 100,000 / day | First 50 million / month included + $1.00 / million rows | | SQL Stored data <sup>5</sup> | 5 GB (total) | 5 GB-month, + $0.20/ GB-month | <details> <summary> Footnotes </summary> <sup>1</sup> Rows read and rows written included limits and rates match <a href="https://developers.cloudflare.com/d1/platform/pricing/">D1 pricing</a>, Cloudflare's serverless SQL database. <sup>2</sup> Key-value methods like <code>get()</code>, <code>put()</code>, <code>delete()</code>, or <code>list(

  • Apache-2.0 ↗

    Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to ca

  • architecture ↗

    name = "atalaya" main = "src/index.ts" compatibility_date = "2026-07-06" compatibility_flags = ["nodejs_compat"] workers_dev = false [cache] enabled = true [assets] directory = "./status-page/dist/client/" binding = "ASSETS" run_worker_first = true [triggers] crons = ["* * * * *", "0 * * * *"] [[d1_databases]] binding = "DB" database_name = "atalaya" database_id = "xxxxxxxx-yyyy-xxxx-iiii-jjjjjjjjjjjj" migrations_dir = "./migrations" [[durable_objects.bindings]] name = "REGIONAL_CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Mo

  • architecture ↗

    worker_first = true [triggers] crons = ["* * * * *", "0 * * * *"] [[d1_databases]] binding = "DB" database_name = "atalaya" database_id = "xxxxxxxx-yyyy-xxxx-iiii-jjjjjjjjjjjj" migrations_dir = "./migrations" [[durable_objects.bindings]] name = "REGIONAL_CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Monitor" default_retries: 2 default_retry_delay_ms: 1000 default_timeout_ms: 5000 default_failure_threshold: 2 alerts: - name: "telegram" type: webhook url: "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" method: POST headers: Content-Ty

  • architecture ↗

    CHECKER_DO" class_name = "RegionalChecker" [[migrations]] tag = "v1" new_sqlite_classes = ["RegionalChecker"] [vars] MONITORS_CONFIG = """ settings: title: "Atalaya Uptime Monitor" default_retries: 2 default_retry_delay_ms: 1000 default_timeout_ms: 5000 default_failure_threshold: 2 alerts: - name: "telegram" type: webhook url: "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" method: POST headers: Content-Type: application/json body_template: | { "chat_id": "${TELEGRAM_CHAT_ID}", "text": "{{monitor.name}} transitioned from status {{status.previous}} to status {{status.current}} {{check.error}}" } - name: "default" type:

Documented public demo screenshot · dcarrillo/atalaya repository contributors; screenshot captured by Cloudsteading ↗. Depicts the upstream project. We have not deployed and tested a fresh installation here.

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

UptimeRobot logoUptimeRobot ↗

Endpoint availability checking, regional checks and publishing a status page; full incident management, on-call escalation and synthetic-browser parity are excluded.

See supporting source ↗
Better Stack logoBetter Stack ↗

Endpoint availability checking, regional checks and publishing a status page; full incident management, on-call escalation and synthetic-browser parity are excluded.

See supporting source ↗
Pingdom logoPingdom ↗

Endpoint availability checking, regional checks and publishing a status page; full incident management, on-call escalation and synthetic-browser parity are excluded.

See supporting source ↗
external SaaS target
varies
external SaaS target
varies
external SaaS target
varies

How it works

The shape of Atalaya on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Services observed in deployment configuration. Routes, jobs and runtime behavior still need verification.

View upstream source ↗
Public interface
Public interface1
Atalaya
Browser, API or documented client interface
↓
App
Atalaya
entry
Cloudflare Workers
Every-minute and hourly scheduled tasks from the supplied template
↓

Configuration and workflow sources

Reviewed commit 5437d1c4357d. Files were read as data; upstream applications and CI jobs were not executed.

Partial source coverage: 3 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.

Deployment configuration · 1 files
wrangler.example.toml ↗

Cloudflare Workers · example/template, excluded from overview · compatibility 2026-07-06

atalaya · default

Entrypoint: src/index.ts

Static assets: ./status-page/dist/client/ · Worker first: true

Cron triggers (UTC): * * * * * · 0 * * * *

Configured route patterns: custom.domain.com

  • DB → D1
  • REGIONAL_CHECKER_DO → Durable Objects · class RegionalChecker
  • ASSETS → Static assets

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

src/index.ts ↗
  • L19 · fetch handler exported · references ASSETS, STATUS_PUBLIC · calls checkAuth, env.ASSETS.fetch, astroMod.default.fetch, responseWithCache.headers.set, console.error, JSON.stringify, String
  • L62 · scheduled handler exported · references MONITORS_CONFIG, DB · calls handleAggregation, interpolateSecrets, parseConfig, prepareChecks, console.warn, JSON.stringify, executeAllChecks, getMonitorStates, processResults, writeCheckResults, updateMonitorStates, Promise.all, actions.alerts.map, sendWebhook, recordAlert, console.info, console.error, String
  • L115 · executeAllChecks calls (conditional paths may differ): checks.map, executeCheck, Promise.all
  • L120 · executeCheck calls (conditional paths may differ): console.info, JSON.stringify, env.REGIONAL_CHECKER_DO.idFromName, env.REGIONAL_CHECKER_DO.get, typedStub.runCheck, typedStub.kill, console.error, String, console.warn, executeLocalCheck
  • L178 · executeLocalCheck calls (conditional paths may differ): console.info, JSON.stringify, executeHttpCheck, executeTcpCheck, executeDnsCheck
  • L195 · sendWebhook calls (conditional paths may differ): formatWebhookPayload, console.error, JSON.stringify, isBlockedURL, fetch, String

Environment references: env.ASSETS · env.STATUS_PUBLIC · env.MONITORS_CONFIG · env.DB · env.REGIONAL_CHECKER_DO

src/lib/auth.ts ↗
  • L16 · timingSafeCompare calls (conditional paths may differ): Promise.all, crypto.subtle.digest, encoder.encode
  • L33 · checkAuth calls (conditional paths may differ): request.headers.get, unauthorizedResponse, authHeader.slice, atob, credentials.indexOf, credentials.slice, Promise.all, timingSafeCompare

Environment references: env.STATUS_PUBLIC · env.STATUS_USERNAME · env.STATUS_PASSWORD

src/aggregation.ts ↗
  • L7 · chunkArray calls (conditional paths may differ): chunks.push, array.slice
  • L26 · handleAggregation calls (conditional paths may differ): Math.floor, Date.now, aggregateHour, deleteOldRawData, deleteOldHourlyData, console.info, JSON.stringify, toISOString
  • L43 · aggregateHour calls (conditional paths may differ): all, bind, database.prepare, result.results.map, stmt.bind, Math.round, chunkArray, database.batch
  • L101 · deleteOldRawData calls (conditional paths may differ): run, bind, database.prepare
  • L106 · deleteOldHourlyData calls (conditional paths may differ): run, bind, database.prepare

Environment references: env.DB

src/checks/dns.ts ↗
  • L8 · executeDnsCheck calls (conditional paths may differ): Date.now, encodeURIComponent, setTimeout, controller.abort, fetch, clearTimeout, sleep, response.json, expectedValues.every, resolvedValues.includes, expectedValues.join, resolvedValues.join
src/checks/http.ts ↗
  • L9 · executeHttpCheck calls (conditional paths may differ): Date.now, isBlockedURL, setTimeout, controller.abort, fetch, clearTimeout, response.text, errorMessages.push, check.expectedBodyContains.trim, bodyText.includes, errorMessages.join, sleep
src/checks/tcp.ts ↗
  • L5 · executeTcpCheck calls (conditional paths may differ): Date.now, check.target.split, Number.parseInt, Number.isNaN, connect, setTimeout, reject, Promise.race, clearTimeout, socket.close, sleep
src/db.ts ↗
  • L5 · chunkArray calls (conditional paths may differ): chunks.push, array.slice
  • L14 · getMonitorStates calls (conditional paths may differ): all, bind, database.prepare
  • L25 · writeCheckResults calls (conditional paths may differ): database.prepare, writes.map, stmt.bind, chunkArray, database.batch
  • L44 · updateMonitorStates calls (conditional paths may differ): database.prepare, updates.map, stmt.bind, chunkArray, database.batch
  • L77 · recordAlert calls (conditional paths may differ): run, bind, database.prepare, Math.floor, Date.now
src/utils/interpolate.ts ↗
  • L1 · interpolateSecrets calls (conditional paths may differ): configYaml.replaceAll
src/utils/ssrf.ts ↗
  • L99 · ipMatchesAnyPrefix calls (conditional paths may differ): ip.startsWith
  • L108 · isBlockedURL calls (conditional paths may differ): url.protocol.toLowerCase, url.hostname.toLowerCase, hostname.replaceAll, LOCALHOST_NAMES.has, strippedHostname.endsWith, hostname.includes, ipMatchesAnyPrefix
src/checks/utils.ts ↗
  • L1 · sleep calls (conditional paths may differ): setTimeout
src/config/config.ts ↗
  • L7 · interpolateEnv calls (conditional paths may differ): content.replaceAll
  • L14 · applyDefaults calls (conditional paths may differ): JSON.stringify, alerts.push, map, isValidRegion, console.warn, Array.isArray, m.maintenance.filter, Date.parse, isNaN, w.start.endsWith, w.end.endsWith, String
  • L141 · parseConfig calls (conditional paths may differ): interpolateEnv, yaml.load, applyDefaults
src/checker/checker.ts ↗
  • L4 · prepareChecks calls (conditional paths may differ): config.monitors.map, Object.keys, String
src/processor/processor.ts ↗
  • L12 · processResults calls (conditional paths may differ): monitorMap.set, stateMap.set, Math.floor, Date.now, monitorMap.get, isInMaintenance, stateMap.get, actions.dbWrites.push, actions.stateUpdates.push, actions.alerts.push
src/alert/webhook.ts ↗
  • L7 · jsonEscape calls (conditional paths may differ): JSON.stringify, escaped.slice
  • L16 · resolveKey calls (conditional paths may differ): key.trim, jsonEscape, statusEmoji, String, resolvers.get, resolver
  • L40 · renderTemplate calls (conditional paths may differ): template.replaceAll, resolveKey
  • L53 · formatWebhookPayload calls (conditional paths may differ): config.alerts.find, config.monitors.find, invertStatus, toISOString, renderTemplate
Build and deployment pipeline · 1 GitHub Actions workflows

Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

CI - Test, Lint, and Build · .github/workflows/ci.yml ↗

Triggers: push, pull_request

test · no job dependencies declared

  1. actions/checkout@v6actions/checkout@v6
  2. Install pnpmpnpm/action-setup@v6
  3. Set up Node.js ${{ matrix.node-version }}actions/setup-node@v6
  4. Install dependenciespnpm install --frozen-lockfile
  5. Run linters and type checkingpnpm check pnpm check:pages
  6. Build status pagepnpm build:pages
  7. Run worker testspnpm test
  8. Run status-page testspnpm test:pages
package.json ↗
  • deploy: pnpm --filter=atalaya-prod-status-page run build && wrangler deploy
  • build:pages: pnpm --filter=atalaya-prod-status-page run build
status-page/package.json ↗
  • build: astro build
  • deploy: echo 'Deploy from root: pnpm run deploy'

Full upstream document by @dcarrillo · README.md · snapshot 5437d1c

Atalaya Uptime Monitor

CI License Node.js TypeScript Cloudflare Workers Astro

Atalaya (Spanish for watchtower) is an uptime & status page monitoring service running on Cloudflare Workers and Durable Objects allowing checks from multiple regions.

It runs entirely on Cloudflare's free tier, so you get a self-hosted uptime monitor and status page without paying for servers. It's aimed at hobbyists who want control over their monitoring and don't mind living inside Cloudflare's ecosystem.

Live example.

:warning: 99% of the code has been generated by an AI agent under human supervision, bearing in mind that I haven't used TypeScript before. You have been warned!

                    🏴‍☠️
                    |
                _  _|_  _
               |;|_|;|_|;|
               \\.    .  /
                \\:  .  /
                 ||:   |
                 ||:.  |
                 ||:  .|
                 ||: , |
                 ||:   |
                 ||: . |
                _||_   |
        __ ----~    ~`---,
__ ,--~'                  ~~----____

Features

  • HTTP/HTTPs, TCP, and DNS monitoring.
  • Regional monitoring from specific Cloudflare locations.
  • Configurable retries with immediate retry on failure.
  • Configurable failure thresholds before alerting.
  • Custom alert templates for notifications (currently only webhooks are supported).
  • Historical data stored in Cloudflare D1.
  • Status page built with Astro 6 SSR, served by the same Worker via Static Assets.
    • 90-day uptime history with daily bars.
    • Response time charts (uPlot) with downtime bands.
    • Basic auth or public access modes.
    • Dark/light mode.
    • Custom banner image support with optional clickable link.

Documentation

Read the docs for setup, configuration, alerts, and more.

Development

# Run worker locally
wrangler dev --test-scheduled

# Run status page locally
pnpm dev:pages

# Trigger cron manually
curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"

Testing

# First build the status page
pnpm build:pages

# Worker tests
pnpm test

# Status page unit tests
pnpm test:pages

# Type checking and linting
pnpm check              # worker (typecheck + lint + format)
pnpm check:pages        # pages (astro check + tsc + lint)

TODO

  • Add support for TLS checks (certificate validity, expiration). Apparently, the Workers API does not support certificate data access, even at the socket level. An external service may be required.
  • Refine the status page to look... well... less AI-generated.
  • Per-monitor maintenance windows
  • Initial support for incident management (manual status overrides, incident timeline).
  • Branded status page (simple custom banner).
  • Add support for notifications other than webhooks.

Frequently asked about Atalaya

What is Atalaya?+

Atalaya is a self-hosted Better Stack/Pingdom alternative built on the Cloudflare developer platform. Monitor endpoints from Cloudflare regions and publish an uptime status page.

What does Atalaya replace?+

Atalaya is listed as an alternative to Better Stack, Pingdom, UptimeRobot. Compare the features and tradeoffs before migrating.

What Cloudflare primitives does Atalaya use?+

Atalaya is built on D1, Durable Objects, Workers.

How much does Atalaya cost to run?+

The documented Atalaya deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs. Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits. Keep aggregate D1 use below 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; a request can touch many rows. Use the configured SQLite Durable Object classes within 100,000 requests/day, 13,000 GB-s duration/day, 5 million SQL rows read/day, 100,000 written/day and 5 GB storage; active sockets consume duration. Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations. Check current Cloudflare pricing before deploying.

Is Atalaya open source?+

The upstream repository declares the Apache-2.0 license. Read its terms at https://raw.githubusercontent.com/dcarrillo/atalaya/5437d1c4357de76f045261aa04974c91c0895ca5/LICENSE. Source code and contributor credit are available at https://github.com/dcarrillo/atalaya.

Discussion · 0

sign in to comment →
No comments yet — be the first.