A self-hosted dashboard for your AI agents' output.
Agents, scripts, and automations send their reports to PulseDeck. You get one clean, searchable place to read them — instead of digging through Slack, email, and terminal logs.
Quick start · What you get · Connect an agent · Documentation
You need Docker and make (plus
openssl, preinstalled on macOS/Linux).
git clone https://github.com/me-shaon/pulsedeck.git pulsedeck && cd pulsedeck
make setup # creates .env and generates strong secrets (idempotent)
docker compose up # pulls the published images; or `make up` to run detachedmake setup is required: the stack refuses to boot without a real
AUTH_SECRET/POSTGRES_PASSWORD, so it can never run on a known value.
Open http://localhost:3000, create your account and workspace, and you're running.
The images are pre-built and published to Docker Hub
(ahmedshamimhassan/pulsedeck-api, ahmedshamimhassan/pulsedeck-web), so
docker compose up never builds locally. Pin a release
with PULSEDECK_VERSION=v1.0.0 docker compose up; omit it for :latest. To
build from source instead — e.g. after editing a Dockerfile — use
make build / make up-build.
Deploy without cloning: because the compose file is self-contained, you only
need three files on the host — docker-compose.yml, .env (from .env.example),
and a valid AUTH_SECRET/POSTGRES_PASSWORD — then docker compose up -d.
Want to watch it fill up? Run the built-in demo agent — it sends realistic reports every 30 seconds:
- In the app, go to Sources → Add source and copy the registration token (
reg_…). - Run the demo agent:
pnpm --filter @pulsedeck/demo dev --url http://localhost:3000 --token reg_xxxxxCharts, streams, and the live feed start populating right away.
Heads up: the demo agent runs with Node 22+ and pnpm on your host — it's a workspace package, not part of the Docker image. If you set up with Docker and don't have Node/pnpm, either install them or skip the demo; PulseDeck itself runs fine without it.
Port already taken? Override it inline, e.g.
WEB_PORT=8080 docker compose up. Defaults: web3000, api3001, postgres5432.
Manual setup for local development and production (no Docker required)
PulseDeck is a standard pnpm monorepo. The only external service it needs is PostgreSQL — Redis is optional (used solely for multi-replica realtime fan-out). The API applies its database migrations automatically on startup, so there is no separate migration step.
- Node.js 22+
- pnpm 9.15+ —
corepack enable && corepack prepare pnpm@9.15.0 --activate - PostgreSQL 16+ — running and reachable
git clone https://github.com/me-shaon/pulsedeck.git pulsedeck && cd pulsedeck
pnpm install --frozen-lockfilepsql -U postgres -c "CREATE USER pulsedeck WITH PASSWORD 'pulsedeck';"
psql -U postgres -c "CREATE DATABASE pulsedeck OWNER pulsedeck;"
# Postgres 15+ locks down the public schema — grant it explicitly:
psql -U postgres -d pulsedeck -c "GRANT ALL ON SCHEMA public TO pulsedeck;"The API reads its config from apps/api/.env (real environment variables always
win). Create it with at least the two required values:
cat > apps/api/.env <<'EOF'
DATABASE_URL=postgres://pulsedeck:pulsedeck@localhost:5432/pulsedeck
AUTH_SECRET=replace-with-32+-random-chars # e.g. `openssl rand -base64 48`
EOFAll other options (retention, OAuth, signup mode, …) are documented in
.env.example. Note DATABASE_URL ships commented out
there: the Docker stack builds its own URL (DB host postgres) from
POSTGRES_PASSWORD, and a localhost value in the root .env would break it.
For this non-Docker path you set DATABASE_URL explicitly (host localhost) in
apps/api/.env, as above.
pnpm devThis starts the API on :3001 and the web dev server on :3000 (Vite
proxies /api → the API, keeping cookies first-party). Migrations run on API
start. Open http://localhost:3000.
The demo agent works the same as above:
pnpm --filter @pulsedeck/demo dev --url http://localhost:3000 --token reg_xxxxx.
Build everything:
pnpm buildThis emits the API bundle to apps/api/dist and the static web bundle to
apps/web/dist.
Start the API (it applies pending migrations, then listens):
cd apps/api
NODE_ENV=production \
DATABASE_URL=postgres://pulsedeck:pulsedeck@localhost:5432/pulsedeck \
AUTH_SECRET=your-32+-char-secret \
BETTER_AUTH_URL=https://your.domain \
PORT=3001 \
node dist/index.jsKeep it alive with a process manager (systemd, pm2, …). BETTER_AUTH_URL must be
the public origin users hit (the web origin below), not the API port.
Serve the web bundle. apps/web/dist is static files. The SPA calls /api
on its own origin, so put a reverse proxy in front that serves the static files
and forwards /api to the API. Example nginx (mirrors the bundled
apps/web/nginx.conf):
server {
listen 80;
server_name your.domain;
root /path/to/pulsedeck/apps/web/dist;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:3001; # keeps the /api prefix
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Server-Sent Events (live updates): flush immediately, don't time out.
proxy_set_header Connection '';
proxy_buffering off;
proxy_read_timeout 1h;
}
location / {
try_files $uri $uri/ /index.html; # SPA client-side routing
}
}Caddy alternative (Caddyfile):
your.domain {
root * /path/to/pulsedeck/apps/web/dist
handle /api/* {
reverse_proxy 127.0.0.1:3001
}
handle {
try_files {path} /index.html
file_server
}
}Put TLS in front (Caddy does this automatically; for nginx use certbot) and set
BETTER_AUTH_URL=https://your.domain to match.
- One inbox for every agent — reports land organized into categories and streams, newest first.
- Rich, structured reports — metrics, charts, tables, timelines, alerts, and status grids, not just walls of text.
- Search everything — full-text search and filters by source, severity, tags, and date.
- Custom dashboards — drag-and-drop widgets to build the view you want.
- Teams — multiple workspaces, roles, and invite links.
- Live updates — new reports appear instantly, no refresh needed.
- Yours to host — one command, only needs PostgreSQL. No SaaS lock-in.
Any agent that can make an HTTP request can send reports — no SDK or special library needed. Register once to get an API key, then POST your reports.
The Add source page in the app gives you a ready-to-paste setup prompt. For the full API, see the documentation.
Want a working example? packages/demo is a complete reference agent you can read and copy.
Out of the box PulseDeck sends no email — invite links surface in the app, and the Forgot password? flow shows a warning that email isn't configured. To enable password-reset delivery (and emailed invites), point PulseDeck at an SMTP server:
EMAIL_PROVIDER=smtp
EMAIL_FROM="PulseDeck <no-reply@yourdomain.com>"
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587 # 587 STARTTLS (default) or 465 with SMTP_SECURE=true
SMTP_USER=...
SMTP_PASS=...Once EMAIL_PROVIDER=smtp and SMTP_HOST are set, the admin banner in Settings
disappears and reset links are delivered by email. Any standard SMTP provider works
(Mailgun, Postmark, SES SMTP, Resend SMTP, your own server, etc.). See
.env.example for all options.
Detailed guides — configuration, deployment, the full agent API, and development setup — live on the docs site:
Quick links for now:
- Configure — all options live in
.env.example. - Develop —
make devruns the whole stack in Docker with hot reload.make helplists every command. - Contribute — see
CONTRIBUTING.md.
PulseDeck is licensed under the GNU AGPL v3 — see LICENSE. Contributions require agreement to the CLA. Running it as a commercial hosted service requires open-sourcing your changes, or a commercial license.