Docs

DocsOperate

Run without Cloudflare

Checkend can also run as one Node process with one SQLite file, for example in Docker. It has the same features and the same API as on Cloudflare.

Install with ce#

The first path is ce server install (see the ce CLI). It installs a signed server release, shows the plan and asks first (--yes for agents), and ends with the setup link of the web UI.

Docker on this machine:

Terminal
ce server install docker

A server over SSH:

Terminal
ce server install ssh root@203.0.113.7 --domain errors.example.com

ce server install docker writes compose.yaml and .env (with new secrets) in ./checkend, and starts nothing: it prints the docker compose command to run. For a server over SSH, see On a server over SSH. Later, ce server upgrade backs up, then upgrades (see Upgrade with ce).

The manual steps below stay an alternative.

With Docker#

Terminal
docker run -p 8787:8787 -v checkend-data:/data -e ADMIN_TOKEN=<at least 32 characters> \
  ghcr.io/checkend/checkend:<version>

The maintainers publish the image for each release (linux/amd64 and linux/arm64), for example ghcr.io/checkend/checkend:0.8.0. You can also build it yourself from the repository: docker build -t checkend ., then use the image checkend.

Or use Docker Compose with examples/docker/compose.yaml. Put the secrets in a .env file next to it, never in the image:

Terminal
ADMIN_TOKEN=<output of: openssl rand -hex 32>
ENCRYPTION_KEYS=k1:<output of: openssl rand -base64 32>

Then run docker compose up -d.

The image:

  • runs as a user that is not root
  • keeps all its data on the /data volume (the SQLite file and the blobs)
  • has a health check (/health)
  • runs the migrations at start

Without Docker#

You need Node 22.18 or later:

Terminal
npm ci --omit=dev
ADMIN_TOKEN=$(openssl rand -hex 32) node src/entry/server.ts

On a server over SSH#

ce server install ssh (see the ce CLI) installs Checkend on an Ubuntu (22.04, 24.04 or 26.04) or Debian (12 or 13) server over SSH:

Terminal
ce server install ssh root@203.0.113.7 --domain errors.example.com

It:

  • uses your SSH agent, and checks the host key of the server. The key must be in your known_hosts, match --host-key <SHA256:…>, or you confirm it. It never skips the check.
  • installs Docker from the apt repository of Docker, if Docker is missing
  • writes compose.yaml (the image pinned by its digest, a volume, restarts and a nightly backup) and .env in /opt/checkend (change it with --dir)
  • starts Checkend behind Caddy, which gets the HTTPS certificate. The domain must point at the server, and ports 80 and 443 must be open. With --tunnel, Checkend uses a Cloudflare Tunnel instead: put the token of the tunnel in CHECKEND_TUNNEL_TOKEN, and route the domain to http://checkend:8787.
  • prints the setup link of the web UI

It shows the plan and asks first (--yes for agents). Run it again to update: the secrets on the server stay. Secrets go to the server only on stdin, never as arguments.

ce server install ssh installs only a signed server release (see Server releases). To install by hand, use Docker on the server.

Settings#

Checkend reads the same environment variables as the Worker (ADMIN_TOKEN, ENCRYPTION_KEYS, RETENTION_DAYS, GITHUB_TOKEN, ...; see Settings and health). These are only for Node:

NameDefaultWhat it does
DATABASE_PATHdata/checkend.sqlite (in the image: /data/checkend.sqlite)The SQLite file.
PORT, HOST8787, 0.0.0.0Where the server listens.
BLOBS_DIRnone (in the image: /data/blobs)A folder for blobs: source maps and large raw payloads.
S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYnoneS3-compatible storage for blobs (also R2), instead of BLOBS_DIR.
SMTP_URL, EMAIL_FROMnoneEmail alerts: smtps://user:password@host:465. smtp:// is only for a local relay without a user and password: with credentials, it must be smtps://, else the server does not start and names the reason.
TRUSTED_PROXIESnoneThe proxies whose X-Forwarded-For Checkend reads: addresses, CIDR ranges, loopback or private, separated by commas.
RATE_LIMIT_PER_MINUTE1000The rate limit for each DSN key. 0 turns it off.
CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKENnoneWorkers AI for AI triage and similar issues, through the REST API and AI Gateway. The token needs Workers AI Read. Without them, there is no AI. On Node, the vectors of similar issues stay in SQLite.

Behind a reverse proxy#

Caution: Checkend serves plain HTTP on port 8787, so tokens and DSN keys would travel in clear text. Put a TLS reverse proxy in front of it (for example Caddy, nginx or Traefik), and publish only the proxy.

  • Set PUBLIC_URL to the public address, for example https://errors.example.com. Without it, the proxy must forward the real Host header: the passkeys, the CSRF check of the web UI, the recipes, llms.txt and the DSNs use the host of the request. A wrong Host makes passkeys work only on that host, and makes the CSRF check trust that host.
  • Set TRUSTED_PROXIES to the proxy (for example private). Without it, every client looks like the proxy, so all clients share one rate limit. All browsers also share one sign-in limit of the web UI (20 tries in 15 minutes), so one client can stop sign-in for everyone.

Checkend reads X-Forwarded-For from the right, and only through trusted proxies, so a client cannot fake its address.

Web UI#

ce server install docker and ce server install ssh make the setup code and print the setup link of the web UI. After the manual steps (Docker or Node by hand), the setup link is not available yet: use development mode only on a local machine, or use ce server install. See Web UI.

What is different from Cloudflare#

CloudflareNode
DatabaseD1One SQLite file
Queue (overflow, webhook deliveries)Cloudflare Queues (optional)A table in the same SQLite file, with retries; failed jobs are kept
ScheduleA Cron Trigger every 5 minutesA timer in the process, every 5 minutes
BlobsR2 (BLOBS)A folder (BLOBS_DIR) or S3-compatible storage
EmailEmail Service (EMAIL)SMTP (SMTP_URL)

On Node, a webhook connects only to the address that passed the URL check, so a DNS change after the check cannot send it to an internal address.

The safety check of scrub patterns measures CPU time on Node, so a busy server does not reject a fast pattern.

When Checkend gets SIGTERM (for example from docker stop), it stops taking requests, writes the events that it already accepted, closes the database, and exits. So after a clean stop, the file checkend.sqlite alone is complete: the recent writes are no longer in a separate -wal file.

Backups and upgrades#

See Backup and upgrade.

Edit this page on GitHubSource: guides/self-host.md

Esc