PulseWatch
Self-hosting

Self-host PulseWatch with Docker

November 15, 2026
PulseWatch Team
8 min read

PulseWatch is fully self-hostable. This post is a copy-paste guide to running the whole stack on your own machine or server with Docker, using the published image ghcr.io/optomake-srl/pulsewatch:latest. By the end you will have the app and a Postgres database on a shared Docker network, a health check you can probe, and notes for running it in production.

TL;DR Create a Docker network, run Postgres on it, then run the PulseWatch image with the right environment variables. The app listens on port 10000 and exposes /health. Everything below is copy-paste ready.

Prerequisites

  • Docker Engine 24+ and the Docker CLI (docker --version).
  • A host with at least 1 GB RAM and 2 vCPU (2 GB+ recommended for production).
  • A domain name and a reverse proxy (Caddy, nginx or Traefik) if you want HTTPS in production.
  • Postgres 16+ - either the bundled container below or a managed database.

Pick a strong random value for every secret. You can generate one with:

openssl rand -hex 32


                    

Environment variables

These are read by the app at startup. PORT, DATABASE_URL, SECRET_KEY, BETTER_AUTH_SECRET and BETTER_AUTH_URL are required.

Variable Required Example / default Notes
PORT yes 10000 HTTP port the app listens on. Keep 10000 unless you change the mapping.
DATABASE_URL yes postgres://pulsewatch:change-me@pulsewatch-db:5432/pulsewatch Postgres connection string. Use the service name as host on the shared network.
SECRET_KEY yes <random 32+ chars> App secret used for signing. Generate with openssl rand.
BETTER_AUTH_SECRET yes <random 32+ chars> Secret for Better Auth sessions and tokens.
BETTER_AUTH_URL yes https://pulsewatch.example.com Public URL of the app. Must match what users open in the browser; used in auth callbacks.
NODE_ENV no production Set to production for production deployments.

Optional integrations

  • OAuth: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET (only if you enable those providers).
  • Sentry: SENTRY_DSN for error reporting.
  • PostHog: NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST for analytics.

2. Run the database

This starts Postgres 16 on the shared network, persists data in a named volume, and exposes port 5432 only on localhost.

docker run -d --name pulsewatch-db   --network pulsewatch   -e POSTGRES_USER=pulsewatch   -e POSTGRES_PASSWORD=change-me   -e POSTGRES_DB=pulsewatch   -v pulsewatch-pg:/var/lib/postgresql/data   -p 127.0.0.1:5432:5432   --restart unless-stopped   postgres:16-alpine

                    

3. Run PulseWatch

Wait a few seconds for Postgres to be ready, then start the app on the same network. Replace the secrets and the public URL with your own.

docker run -d --name pulsewatch   --network pulsewatch   -p 10000:10000   -e PORT=10000   -e DATABASE_URL="postgres://pulsewatch:change-me@pulsewatch-db:5432/pulsewatch"   -e SECRET_KEY="replace-with-openssl-rand-hex-32"   -e BETTER_AUTH_SECRET="replace-with-openssl-rand-hex-32"   -e BETTER_AUTH_URL="https://pulsewatch.example.com"   -e NODE_ENV="production"   --restart unless-stopped   ghcr.io/optomake-srl/pulsewatch:latest

                    

4. Health check

The app exposes /health on port 10000. Probe it to confirm everything is up:

curl -fsS http://localhost:10000/health

                    

A 200 OK means the app and its database connection are healthy. Point your reverse proxy at port 10000 and set BETTER_AUTH_URL to the public HTTPS URL.

Production guidance

  • Managed Postgres: for production, point DATABASE_URL at a managed database (Neon, Supabase, RDS, etc.) and remove the bundled pulsewatch-db container. Back up with your provider's tooling or pg_dump.
  • Remove bundled db: if you keep the container, restrict port 5432 to localhost (as above) and never expose it publicly.
  • Watchtower: auto-update the app container when a new image is published:
docker run -d --name watchtower   --restart unless-stopped   -v /var/run/docker.sock:/var/run/docker.sock   containrrr/watchtower --cleanup --interval 3600 pulsewatch

                    

Docker Swarm build note: the published image is already multi-arch. If you build your own image for Swarm, build for both architectures and push to your registry: docker buildx build --platform linux/amd64,linux/arm64 -t your-registry/pulsewatch:latest --push . Reference the image (not a local build) in your stack file.

Optional: run a worker (probe)

PulseWatch Probe lets you report region-level check results from your own host. It ships as a separate image:

docker run -d --name pulsewatch-probe   --restart unless-stopped   -e PULSEWATCH_URL=https://pulsewatch.example.com   -e REGION=eu   ghcr.io/optomake-srl/pulsewatch-probe:latest

                    

The probe prints a pairing URL and code in its logs; approve it while signed in at /pair-probe and it starts reporting. See the PulseWatch Probe post for pairing details.

The PulseWatch Team

Share
PW
PulseWatch TeamTransparent infrastructure, always. All posts →
Older post →

💬 Comments

Questions, corrections or thoughts on this post? Join the thread below.

PulseWatch, transparent infrastructure, always

© 2026 PulseWatch. Building reliable uptime monitoring.

Home · All posts · App · Status · Status Page · ↑ Top