Skip to main content

Self-Hosted Collector

The self-hosted collector is a lightweight binary you run on your own infrastructure. Use this mode when your database is not publicly reachable, or when you want full control over the collector.


Before You Start

  1. Sign in at app.pgpulse.io.
  2. Click Add Instance and select Self-Hosted.
  3. Copy your API key (pk_live_...) from Settings → API Keys.

Postgres Requirements

RequirementDetail
PostgreSQL version12 or later
Rolepg_monitor membership
Extensions (optional)pg_stat_statements — enables query throughput metrics. pg_buffercache — enables memory metrics. The collector skips these gracefully if not installed.
Connections used1 persistent connection (+ 1 per database if multi-database mode is enabled)
CREATE USER pgpulse_monitor WITH PASSWORD 'strong-password';
GRANT pg_monitor TO pgpulse_monitor;

Configuration File

Create a pgpulse-collector.yaml file on the machine that will run the collector.

Minimal config

postgres:
host: "your-db-host"
port: 5432
user: "pgpulse_monitor"
password: "strong-password"
database: "postgres"
sslmode: "require"

api:
endpoint: "https://api.pgpulse.io/v1/collect"
api_key: "pk_live_your_api_key_here"

collector:
instance_name: "prod-primary"

Connection Scenarios

Supabase — Direct Connection

postgres:
host: "db.<your-project-ref>.supabase.co"
port: 5432
user: "postgres"
password: "<your-db-password>"
database: "postgres"
sslmode: "require"

Supabase — Pooler Connection

If you connect via the Supabase pooler, embed your project ref in the username.

postgres:
host: "aws-<zone-id>-<region>.pooler.supabase.com"
port: 5432
user: "postgres.<your-project-ref>"
password: "<your-db-password>"
database: "postgres"
sslmode: "require"

Session mode only — transaction mode is not supported.

Bare Metal / VPS / Any Cloud Provider

postgres:
host: "<host-ip>"
port: 5432
user: "pgpulse_monitor"
password: "strong-password"
database: "postgres"
sslmode: "prefer"

Connection String (DSN)

If you store a full DSN in a secret manager, use connection_string instead of individual fields.

postgres:
connection_string: "postgresql://pgpulse_monitor:password@host:5432/postgres?sslmode=require"

Multiple Databases on One Instance

Enable collect_all_databases to cover all databases on the instance (useful for bloat, vacuum health, and index stats per database).

collector:
instance_name: "prod-primary"
collect_all_databases: true

# Optional allow/deny lists
include_databases:
- "postgres"
- "app_prod"
exclude_databases:
- "analytics_shadow"

The Pulse score stays a single per-instance number. The dashboard shows which database drove a domain's score.


Deployment

docker run -d \
--name pgpulse-collector \
--restart unless-stopped \
-e PGPULSE_API_KEY=pk_live_your_api_key \
-e PGPULSE_POSTGRES_HOST=your-db-host \
-e PGPULSE_POSTGRES_USER=pgpulse_monitor \
-e PGPULSE_POSTGRES_PASSWORD=strong-password \
-e PGPULSE_POSTGRES_DATABASE=postgres \
-e PGPULSE_POSTGRES_SSLMODE=require \
-e PGPULSE_COLLECTOR_INSTANCE_NAME=prod-primary \
-v pgpulse-buffer:/var/lib/pgpulse/buffer \
pgpulse/collector:latest

Mount the buffer volume so data survives container restarts.


systemd (Linux)

# Download the binary
curl -L https://releases.pgpulse.io/collector/latest/pgpulse-collector-linux-amd64 \
-o /usr/local/bin/pgpulse-collector
chmod +x /usr/local/bin/pgpulse-collector

# Dedicated system user
useradd --system --no-create-home --shell /bin/false pgpulse

# Directories
mkdir -p /etc/pgpulse /var/lib/pgpulse/buffer
chown pgpulse:pgpulse /var/lib/pgpulse/buffer

# Config
cp pgpulse-collector.yaml /etc/pgpulse/collector.yaml
chown root:pgpulse /etc/pgpulse/collector.yaml
chmod 640 /etc/pgpulse/collector.yaml

Create /etc/systemd/system/pgpulse-collector.service:

[Unit]
Description=pgpulse Postgres Collector
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=pgpulse
ExecStart=/usr/local/bin/pgpulse-collector --config /etc/pgpulse/collector.yaml
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now pgpulse-collector
journalctl -u pgpulse-collector -f

Environment Variables (no config file)

Useful for Heroku, Render, Railway, and similar PaaS platforms.

export PGPULSE_API_KEY=pk_live_your_api_key
export PGPULSE_POSTGRES_HOST=your-db-host
export PGPULSE_POSTGRES_PORT=5432
export PGPULSE_POSTGRES_USER=pgpulse_monitor
export PGPULSE_POSTGRES_PASSWORD=strong-password
export PGPULSE_POSTGRES_DATABASE=postgres
export PGPULSE_POSTGRES_SSLMODE=require
export PGPULSE_COLLECTOR_INSTANCE_NAME=prod-primary

./pgpulse-collector
VariableDescription
PGPULSE_API_KEYRequired. Your pk_live_ API key.
PGPULSE_POSTGRES_CONNECTION_STRINGFull DSN — overrides individual fields.
PGPULSE_POSTGRES_HOSTPostgres host.
PGPULSE_POSTGRES_PORTPostgres port (default 5432).
PGPULSE_POSTGRES_USERPostgres user.
PGPULSE_POSTGRES_PASSWORDPostgres password.
PGPULSE_POSTGRES_DATABASEDatabase name.
PGPULSE_POSTGRES_SSLMODEdisable · prefer · require · verify-full
PGPULSE_COLLECTOR_INSTANCE_NAMELabel shown on the dashboard.
PGPULSE_COLLECTOR_COLLECT_ALL_DATABASESSet true to enable multi-database mode.
PGPULSE_LOGGING_LEVELdebug · info · warn · error (default info)
PGPULSE_LOGGING_FORMATjson · text (default json)

Validate Before Going Live

./pgpulse-collector --config collector.yaml --validate

Checks your config, confirms the Postgres host is reachable, opens a connection, and verifies your API key — then exits. Exit code 0 means everything is good.


Troubleshooting

SymptomLikely causeFix
FATAL: PGPULSE_API_KEY is requiredNo API key setAdd api.api_key to your YAML or set PGPULSE_API_KEY.
preflight TCP check failedPostgres host unreachableCheck the host, port, and any firewall rules.
401 at startupInvalid API keyRe-copy the key from the dashboard.
403 at startupHost doesn't match registered instanceFor Supabase pooler, make sure the project ref is in the username (postgres.<ref>).
Pulse not updating on dashboardCollector not sending dataRun --validate and check the logs with journalctl -u pgpulse-collector -f.