Open-source project
basecamp/upright avatar
basecamp/upright

Upright runs Playwright probes from several VPS sites and exports Prometheus metrics

Synthetic monitoring engine with Playwright and Prometheus metrics

839 stars32 forksRubyMIT

At a glance

What is it?
Upright is a Rails engine for self hosted synthetic monitoring, with browser, HTTP, SMTP, MCP and traceroute probes run from multiple geographic sites and reported as Prometheus metrics that AlertManager can act on. It is a Rails app you host yourself and deploy with Kamal, and it deliberately leaves notifications and hosting to other tools.
Who is it for?
Upright suits a team that wants to know whether a user flow still works from outside its own network, and that already runs Rails, Prometheus and AlertManager, since the engine supplies the probes, the site model and the metric plumbing rather than a whole observability stack. It is the wrong tool if you want a hosted service or if you need a browser based product analytics suite, because notifications and hosting are explicitly left out.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Synthetic checks from several places, not from your office

Upright is a self hosted synthetic monitoring system, and its stated scope is a framework for running health check probes from multiple geographic sites and reporting metrics via Prometheus. Alerts are then configured with AlertManager.

The multi site part is the core idea. A probe that only runs from your own network tells you whether the service is up from inside your network, which is exactly the view that misses a DNS problem, a regional outage or an ISP level failure. Upright models each location as a site in a config file, with a code, a city, a country and a geohash, and a site identifies itself through a SITE_SUBDOMAIN environment variable configured in the Kamal deploy configuration.

The audience is an operations or platform team that already has Prometheus and AlertManager in place and wants browser-level checks added to them. The dashboard shows a site overview with a world map, thirty day uptime history, and probe status across all sites.

Two things are explicitly not included. Notifications are left to AlertManager, which the project includes for that purpose, and hosting is left to you, with a VPS from a provider such as DigitalOcean or Hetzner named as the intended route.

Five probe types, from browser flows to network paths

The probe types cover several different failure modes, and the range is broader than a simple HTTP check.

Playwright probes are browser based checks for user flows, with video recording and logs, which is the only type that can tell you a checkout button stopped working while the site still returns 200. The JavaScript dependency is pinned in the package.json, where the playwright entry is at version 1.59.0.

HTTP probes are simple health checks with configurable expected status codes. SMTP probes verify mail servers by checking the EHLO handshake, which catches a mail server that is up but not accepting mail properly. MCP probes are sign-in discovery checks for remote MCP servers, a genuinely new category that reflects the protocol arriving in production stacks.

Traceroute probes do network path analysis with hop by hop latency tracking, so a slowdown can be attributed to a specific hop rather than just observed at the endpoint.

Each probe is defined in the probes directory, which the install generator creates for the HTTP, SMTP and traceroute YAML configs and for the Playwright probe classes.

The install generator sets up more than a database

Upright is designed to be run in its own Rails app and deployed with Kamal, so the quick start creates a new Rails application and installs the engine into it:

bash
rails new my-upright --database=sqlite3 --skip-test
cd my-upright
bundle add upright
bin/rails generate upright:install
bin/rails db:prepare

The generator produces a substantial set of files, and each one tells you something about the design.

The database configuration is split into three, with primary holding probe results, persistent holding rollups and incidents migrated from the gem, and queue holding Solid Queue. The recurring configuration file holds the probe schedules along with the engine's housekeeping, health, rollup, incident and maintenance jobs. There are initializers for the engine configuration and for a content security policy ready to enable covering the engine's CDN dependencies, a sites file for the VPS definitions, a Prometheus configuration, an AlertManager configuration, an OpenTelemetry Collector configuration, and a deploy configuration with a Dockerfile where Prometheus and Alertmanager are pinned by image digest.

Two details stand out. The Kamal secrets and a pre-deploy hook are created together, and the hook refuses to deploy while a token is missing. The engine is also mounted at the root of your routes.

Subdomains carry the routing, so a site is a subdomain

Upright uses subdomain based routing, and the README explains the consequences plainly. The app subdomain is the admin interface, while site specific subdomains such as nyc and lon show the probe results for that location. The .localhost top level domain resolves to 127.0.0.1 on most systems, which is why the quick start tells you to visit http://app.my-upright.localhost:3000.

The production hostname is set in the engine initializer:

ruby
# config/initializers/upright.rb
Upright.configure do |config|
  config.hostname = "upright.com"
end

and for local development the hostname defaults to the service name followed by .localhost.

The site definitions themselves are a YAML list, where each entry carries a code, a city, a country, a geohash and a provider, and two optional flags give a site a role beyond running probes:

yaml
shared:
  sites:
    - code: nyc
      city: New York City
      country: US
      geohash: dr5reg
      provider: digitalocean

    - code: ams
      city: Amsterdam
      country: NL
      geohash: u17982
      provider: digitalocean
      stores_metrics: true
      primary: true

A site marked primary serves the app and status hostnames and runs the jobs that write to the shared database, and a site marked stores_metrics runs a local Prometheus and Alertmanager. The project links to a document describing what each role changes, the health metrics every site exports, and how the daily rollups read across them.

Two bearer tokens with one job each

The machine authentication model is unusually precise, and it is worth reading the table rather than skimming it.

Two bearer tokens protect the Prometheus and Alertmanager proxies. PROMETHEUS_OTLP_TOKEN is presented by the OpenTelemetry collector on every site and allows exactly one thing, a POST to the OTLP metrics endpoint, and nothing else. METRICS_READ_TOKEN is presented by peer sites computing rollups and by tooling, and allows GET and HEAD on the Prometheus and Alertmanager paths, except the lifecycle endpoints.

Generate each with bin/rails secret, set them as Kamal secrets with the same two values on every site, and keep them out of git:

sh
export PROMETHEUS_OTLP_TOKEN=$(bin/rails secret)
export METRICS_READ_TOKEN=$(bin/rails secret)
bin/kamal deploy

Outside development and test the application refuses to boot when either token is missing or when both hold the same value, and the pre-deploy hook refuses to deploy for the same reasons before any container is replaced. A token cannot reach any other route, and the admin interface uses a session cookie instead.

This is a narrow permissions design, and the split between a write token for collectors and a read token for peers is what keeps a site that only runs probes from being able to write metrics.

Authentication fails closed, and the stack behind the engine

The admin interface uses static credentials by default, with the username admin and the password taken from the ADMIN_PASSWORD environment variable. The README is emphatic that there is no default password, that ADMIN_PASSWORD must be set, and that sign-in fails closed without it. The generated deploy configuration already lists it as a Kamal secret, and for local development you export it yourself.

OmniAuth integration is available for any OIDC provider, so a team that already has an identity provider can move off static credentials.

The components list describes the surrounding stack. The engine is a Rails engine, the database is SQLite, background and recurring work runs on Solid Queue, and Mission Control provides a jobs interface for monitoring Solid Queue and manually enqueueing probes. Kamal handles deployments, Prometheus provides the metrics for uptime queries and alerting, AlertManager does the notifications, and the OpenTelemetry Collector can ship logs, metrics and traces to any OTLP compatible endpoint.

For observability the README lists OTLP compatibility, Prometheus metrics, OpenTelemetry tracing and AlertManager support together, which is the combination that lets a browser probe result end up in the same trace and metric stream as the rest of your stack.

An engine in the 0.x line, built for a Rails host app

The release history is young. v0.4.0 was tagged on 2026-09-07, v0.3.0 on 2026-04-09 and v0.2.0 on 2026-02-16. The repository is not archived and the last push was on 2026-10-21, so development is current.

The version numbers below 1.0 mean the engine API is still moving, and the presence of an UPGRADING.md file at the root says the project expects people to read about breaking changes between versions. There is also a RELEASING.md, a SECURITY.md, a CHANGELOG.md and a CONTRIBUTING.md, so the release process is documented rather than improvised.

The tooling is modern Rails. A mise.toml pins the toolchain, there is a rubocop configuration, a package.json for the Playwright dependency, and both AGENTS.md and CLAUDE.md files at the root, which suggests the project is developed with AI assistants in the loop and expects contributors to follow the same guidance.

The licence is MIT, and the repository also carries a Gemfile, Gemfile.lock, Rakefile, an upright.gemspec and an app directory, which is the shape of a gem that is both a library and a mountable application.

Editorial conclusion

Upright suits a team that wants to know whether a user flow still works from outside its own network, and that already runs Rails, Prometheus and AlertManager, since the engine supplies the probes, the site model and the metric plumbing rather than a whole observability stack. It is the wrong tool if you want a hosted service or if you need a browser based product analytics suite, because notifications and hosting are explicitly left out. Before deploying it, run bin/rails generate upright:install, set ADMIN_PASSWORD and both bearer tokens with bin/rails secret, and decide which site is primary and which stores metrics, since those two roles change what each node does.

Frequently asked questions

How do I install Upright?

Create a Rails app with rails new my-upright --database=sqlite3 --skip-test, add the gem with bundle add upright, then run bin/rails generate upright:install and bin/rails db:prepare. Upright is designed to run in its own Rails app and to be deployed with Kamal.

What kinds of probes can Upright run?

Playwright probes run browser based user flows with video recording and logs, HTTP probes do status code checks, SMTP probes verify the EHLO handshake, MCP probes check sign-in discovery on remote MCP servers, and traceroute probes analyse the network path with hop by hop latency.

How do the two bearer tokens differ in Upright?

PROMETHEUS_OTLP_TOKEN, presented by the OpenTelemetry collector, allows only a POST to the OTLP metrics endpoint, while METRICS_READ_TOKEN, presented by peer sites and tooling, allows GET and HEAD on the Prometheus and Alertmanager paths except the lifecycle endpoints. Outside development and test the app refuses to boot if either is missing or if both hold the same value.

Official sources

  1. basecamp/upright on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/basecamp-upright.svg)](https://hysenlabs.com/projects/basecamp-upright)

Was this useful?