Everything Pug’s backend does at runtime is a sub-command of a single binary, pug. The server, every worker, the database migrations, seeding, email tooling and the domain commands all live in it - there’s nothing else to install. This page is the complete command tree.
go run ./cmd/pug <command> # from a source checkout
./bin/pug <command> # after `make build`Every command loads .env from the working directory if present, then falls back to the process environment (see Configuration).
Command tree
The same binary runs three ways - see Overview > Processes: everything-in-one (pug dev), one process per sub-command (production), or single-purpose standalone binaries / images (pug-worker-events, …) that each map to one of these commands.
server
pug serverStarts the Connect RPC API server on :3000 (override with PUG_SERVER_PORT). Handles both SDK ingestion and dashboard/API reads, and serves /healthz and /readyz probes. On ingestion it enriches events (geo, user-agent, bot detection) from the request headers before publishing them to NATS - so enrichment happens here, not in the events worker. This is the only process that listens for public traffic.
dev
pug dev # `pug start` is an aliasRuns the API server and every worker together in one process - the fastest way to get a complete, working stack up. On boot it prints the server URL, the reachable infrastructure, and the active worker list; Ctrl+C stops everything cleanly.
It starts the server plus the events, compliance, and three profile workers unconditionally; the email worker starts only if email is configured, and the demo worker only if PUG_DEMO_ENABLED=true. Worker health endpoints are disabled here (probing is for orchestrated deployments).
pug dev is built for local development, but it’s also a legitimate way to run a small single-node instance. The trade-off versus separate processes is that you can’t scale or restart components independently - for production at any real volume, run one process per role instead (Deployment).
worker ...
Each worker is a standalone NATS JetStream consumer. Run one per command; scale the events worker with multiple replicas for high ingest.
| Command | Consumes / does |
|---|---|
pug worker events |
Consumes ingested events from JetStream and writes them to ClickHouse. The one to scale horizontally. |
pug worker email |
Sends transactional email (magic links, invites, notifications). |
pug worker compliance |
GDPR/DPDP data-subject erasure, export, and retention. |
pug worker profile identify |
Applies identify calls to profiles. |
pug worker profile alias |
Merges identities via alias. |
pug worker profile upsert |
Writes profile property updates. |
pug worker demo |
Generates rolling demo traffic. Idles unless PUG_DEMO_ENABLED=true - a normal self-host does not run it. |
Each worker serves /healthz + /readyz on PUG_WORKER_HEALTH_ADDR (default :8090; off to disable) - see Health checks.
Migrations
Run once before the first boot and again on every upgrade; all are idempotent.
| Command | What it does | Flags |
|---|---|---|
pug postgres migrate |
Applies Postgres schema migrations. | --direction/-d up|down (default up), --num/-n N (steps; 0 = all) |
pug nats migrate |
Creates the JetStream streams and consumers. | - |
pug clickhouse migrate |
Applies ClickHouse schema migrations. | --direction/-d up|down (default up), --num/-n N |
pug postgres migrate
pug nats migrate
pug clickhouse migrate
pug postgres migrate --direction down --num 1 # roll back one Postgres migrationConnection details come from DATABASE_URL, NATS_URL, and CLICKHOUSE_URL.
seed
pug seed # ~500k demo events by default
pug seed --count 10000 # keep it light on a laptopSeeds the demo project with synthetic data so you have something to query immediately. It resets Postgres and ClickHouse, then runs the demo flow: ensures the demo account, backfills events, seeds profiles only for the users those events belong to, and copies them into ClickHouse. (This is the one-shot version of what the demo worker does continuously.)
| Flag | Default | Description |
|---|---|---|
--count / -c |
500000 |
Total number of events to generate. |
--batch / -b |
10000 |
Events per ClickHouse insert batch. |
--no-reset |
off | Skip the migrate down/up reset; truncate the demo tables and re-seed instead. |
Run migrations first (or let
seedreset the stores). Not for production - it targets the demo project specifically.
email preview
pug email preview magic_link # render to HTML on stdout
pug email preview invite --text # render the plaintext twin
pug email preview provider_test --out test.htmlRenders a transactional email to HTML (or --text for the plaintext version) without sending anything - useful for checking branding (PUG_EMAIL_LOGO_URL, PUG_DASHBOARD_BASE_URL) before going live. Kinds: magic_link, invite, provider_test. --out FILE writes to a file instead of stdout.
This is the top-level
pug emailtooling group - distinct frompug worker email, which is the process that actually delivers mail.
domains
pug domains verify <org-id> acme.com # mark the org's domain verified, without DNS
pug domains show acme.com # list every org that added the domain
pug domains release <org-id> acme.com # drop one org's claim, e.g. in a dispute
pug domains unenforce acme.com # turn Require SSO off in every orgOperator commands for the domains that orgs verify under Settings > SSO & domains (see SSO & domains). They connect only to Postgres (DATABASE_URL). They leave every org setting to the org’s admins, except unenforce.
| Command | What it does |
|---|---|
verify <org-id> <domain> |
Marks the domain verified for the org with no DNS check, adding it if the org hasn’t. For a server that can’t see public DNS, or a support case. The dashboard shows Verified by your administrator, and setting changes stop re-checking the TXT record. A claim the org verified through DNS switches to operator-verified. |
show <domain> |
Lists every org that added the domain, with its status and settings. |
release <org-id> <domain> |
Drops one org’s claim. People who joined through it stay members. Unlike removing the domain in the dashboard, it works while the domain requires SSO through that org’s connection. |
unenforce <domain> |
Turns Require SSO off for the domain in every org. It’s the way back when SSO breaks and nobody on the domain can sign in, admins included. |
Each command ends by printing the domain’s claims:
$ pug domains show acme.com
ORG NAME STATUS AUTO-JOIN ORG CREATION SSO SEEN REQUIRE SSO
cs5kq0j1n4s7b2e8m3dg "Acme" verified by dns 2026-10-01 ORG_ROLE_VIEWER restricted 2026-10-02 on
cs7m2p9q4r1t6v8b0c3d "Acme Labs" pending off allowed never offSSO SEEN is the first SSO sign-in on the domain after the claim was verified. Require SSO can’t be turned on before it. Admins find their org’s ID under Settings > Organization. Logs go to stderr, so stdout holds only the report.
Further reading
- Deployment: run these commands as production processes
- Development: the local workflow and
maketargets that wrap them - Configuration: the environment every command reads