⌁ Checkpulse

Self-hosting

Checkpulse runs as two services on one PostgreSQL database:

ServiceCommandPurpose
ApplicationcheckpulseDashboard, pings, API, MCP, deadlines, and alert delivery. Runs migrations at startup. Must run continuously.
Admin consolecheckpulse-adminAccounts, sign-up, limits, and invitations. Connects as the restricted checkpulse_admin role. Can sleep when idle.

Both are in the same Docker image; the admin console only needs a different start command.

Database roles

The application's database user creates the checkpulse_admin role during migrations, so it needs CREATEROLE (a superuser, such as the default user of a managed PostgreSQL service, works). The role can read account details and aggregate counts, and change account status, limits, and settings. It has no privileges on projects, checks, pings, channels, alerts, or API keys, so the admin console cannot show them even if it is compromised. People with direct database access can still read everything.

Set ADMIN_DATABASE_PASSWORD on the application to let the role log in, then connect the admin console with that password.

Application settings

VariablePurpose
DATABASE_URLPostgreSQL connection URL. Required.
PUBLIC_URLThe HTTPS origin users visit, such as https://checkpulse.example.
ADMIN_DATABASE_PASSWORDPassword for the checkpulse_admin role.
CONTACT_EMAILShown on the terms and privacy pages.
EMAIL_PROVIDERresend, postmark, or smtp. Sign-up, password reset, and email alerts need it.
EMAIL_FROMSender address on a domain verified with the provider.
RESEND_API_KEY, POSTMARK_SERVER_TOKENProvider credentials.
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_MODESMTP settings. Port 587 uses STARTTLS; SMTP_MODE=tls uses implicit TLS; plain is allowed only for local development.
TRUSTED_PROXY_HEADERHeader with the client address. Defaults to X-Real-IP on Railway; set none when the app is reached directly.
ALLOW_PRIVATE_WEBHOOKStrue allows HTTP and private-network destinations. Local development only.

Admin console settings

VariablePurpose
DATABASE_URL, or PGHOST, PGPORT, PGDATABASE, PGUSER=checkpulse_admin, PGPASSWORDConnection as the restricted role.
PUBLIC_URLThe console's own HTTPS origin.
APP_URLThe application's origin, used in invitation links.
ADMIN_EMAIL, ADMIN_PASSWORDCreate the first operator on first start. They can be removed afterwards.

Operators must set up two-factor sign-in the first time they sign in.

Local development

cp .env.example .env   # choose ADMIN_PASSWORD
docker compose up --build -d

The app is at http://localhost:8080, the admin console at http://localhost:8081, and the Mailpit inbox (all email) at http://localhost:8025. Sign in to the admin console, then create an invitation or open sign-up to make your first account.

Without the admin console, checkpulse invite you@example.com prints a single-use sign-up link.

Email

Railway disables outbound SMTP below its Pro plan, so hosted deployments should use an email API. Resend's free tier (3,000 emails a month, 100 a day) is enough to start; Postmark costs about $15 a month for 10,000 and has a strong reputation for transactional mail. Either needs a domain you own, verified with SPF and DKIM.

Operations

  • Backups: enable your database provider's backups before inviting users.
  • Monitor the monitor: Checkpulse cannot alert about its own downtime. Point an external heartbeat service at a check that a separate machine pings, or watch /healthz from outside.
  • Retention: history is trimmed hourly to each account's limit; delivered notifications are kept 30 days; audit logs and closed incidents 400 days; deleted checks 7 days.
  • Upgrades: migrations run automatically when the application starts, inside a transaction guarded by an advisory lock. The admin console waits for them.

View as Markdown