Deployment

Pulse is one long-running Node process and one database. This page covers the bundled TimescaleDB setup and the things any host needs to get right: a persistent archive, an always-running process, and access control.

What a deployment needs#

TimescaleDB with Docker Compose#

The repository ships a docker-compose.yml with one service, the database. Pulse itself runs on the host.

services:
  db:
    image: timescale/timescaledb:latest-pg17
    environment:
      POSTGRES_USER: pulse
      POSTGRES_PASSWORD: pulse
      POSTGRES_DB: pulse
    ports:
      - "5544:5432"
    volumes:
      - pulse-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pulse"]
      interval: 5s
      retries: 10
volumes:
  pulse-db:
npm run db:up
echo 'DATABASE_URL=postgres://pulse:pulse@localhost:5544/pulse' >> .env
npm start

On first start Pulse applies the migration and converts the time-series tables to compressed hypertables, see Data model.

Change the password for anything shared. The compose file uses pulse as user, password and database, and publishes the port. That is fine on your own machine. On a server, set a real password, do not publish port 5432 to the internet, and update DATABASE_URL to match.

Run as a service#

A systemd unit for a source checkout or an install from npm. Adjust the paths and user.

[Unit]
Description=Pulse market intelligence
After=network-online.target

[Service]
User=pulse
WorkingDirectory=/opt/pulse
EnvironmentFile=/opt/pulse/.env
ExecStart=/usr/bin/node src/main.ts
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now pulse
journalctl -u pulse -f

Restart=always matters: the firehose reconnects by itself, but a crashed process needs the supervisor.

Container#

The repository does not include a Dockerfile. This is an example that installs the published package. Build it yourself and test it before relying on it.

FROM node:24-slim
RUN npm install -g pulse-trenches
ENV PORT=8787
EXPOSE 8787
CMD ["pulse", "start"]
docker build -t pulse .
docker run -d --name pulse -p 8787:8787 --env-file .env \
  -v pulse-data:/data -e PGLITE_DIR=/data/pglite pulse

With DATABASE_URL set to an external Postgres, the volume and PGLITE_DIR are not needed.

Cloud Run and other serverless hosts#

Pulse is a stateful worker, so scale-to-zero platforms need care. On Google Cloud Run:

gcloud run deploy pulse --image IMAGE \
  --min-instances 1 --max-instances 1 --no-cpu-throttling \
  --set-env-vars DATABASE_URL=...,PUBLIC_URL=https://...

A small always-on VM or any container host with a persistent disk is simpler and usually cheaper for a process that never sleeps.

Exposing the dashboard and API#

Pulse has no authentication and serves CORS for all origins. It binds to every network interface on PORT. Choose one:

All routes are read-only, but the archive includes labelled wallet data and tracked-trade history you may not want public.

Backups and upgrades#

Running only part of it#

GoalHow
Collector and API without the firehoseSTREAMS=off or --no-stream. Wallet scores stay empty because no trades are recorded.
Firehose and collector without the web server--no-server
Cron-style collectionpulse collect --jobs on a schedule. No firehose, but snapshots, statuses and classification all run.
Check health from a monitorGET /api/health returns lastCycle. Alert when it is older than a few cycles.