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.