Everything a self-hosted Pug backend runs is a sub-command of a single binary, pug. The server, every worker, the scheduled jobs, the database migrations, seeding, email tooling and the operator commands all live in it - there’s nothing else to install. (Pug Cloud’s two billing jobs ship only as standalone binaries; a self-hosted instance doesn’t run them.) 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. Variables already set in the process environment win over the file (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, the worker list, and the scheduled jobs it doesn’t run; 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). It never runs cron purge or cron usage - run those by hand, or schedule them.
pug dev is built for local development, but it’s also a legitimate way to run a small low-volume 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 except demo is a NATS JetStream consumer; demo writes straight to ClickHouse. 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, provider tests). |
pug worker compliance |
GDPR/DPDP data-subject erasure. |
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 except demo serves /healthz + /readyz on PUG_WORKER_HEALTH_ADDR (default :8090; off to disable) - see Health checks.
cron
pug cron purge # erase deleted projects
pug cron usage # meter event usageEach runs one pass of a scheduled job and exits - non-zero when the pass failed, which is a scheduler’s only success signal. Neither runs on its own: schedule purge every 5 minutes and usage hourly. A pass holds a Postgres advisory lock, so an overlapping run exits cleanly. See Scheduled jobs.
| Command | What it does |
|---|---|
pug cron purge |
Erases the data of deleted projects from ClickHouse and Postgres. Until it runs, a deleted project is hidden and its keys are dead, but its data stays. |
pug cron usage |
Counts each project’s events per day for the dashboard’s usage view. PUG_USAGE_RESCAN_DAYS sets how far back each pass recomputes. |
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 (steps; 0 = all) |
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. The migrations are read from schema/ under the working directory, so run these from the repo root (or a folder holding its schema/).
Always pass
--numwith--direction down. Its default,0, means all:--direction downon its own rolls back every migration and drops every table.
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 accounts, 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.)
The demo accounts, woof@pug.sh and snoop@pug.sh, are read-only and can’t sign in with a password. To browse the seeded data, set PUG_DEMO_ADMIN_PASSWORD (16 characters or more) before seeding: the seeder then creates admin@pug.example, an admin of the demo org, with that password. Or set PUG_DEMO_ENABLED=true and open the dashboard’s /demo page.
| 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; delete the demo project’s existing rows and re-seed instead. Other projects are left alone. |
Run migrations first if you pass
--no-reset; the default reset applies them itself.
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.
projects delete
pug projects delete <project-id> --actor "alice/TICKET-1"Deletes a project the way an org admin’s delete does, without the admin check - for a project no admin can reach. It hides the project, revokes its API keys, share links, campaigns and push credential, and queues its data for cron purge to erase. --actor is required and is recorded on the deletion. Running it again changes nothing while the deletion is open, and reopens a finished one.
Run it with the server’s environment, not a checkout’s .env: it clears the API-key cache in the server’s Dragonfly.
billing
pug billing show <org-id> --history
pug billing set <org-id> --plan free --anchor-day 15 --actor "alice/TICKET-2"
pug billing clear <org-id> --actor "alice/TICKET-3"Operator commands for an org’s entitlement: its plan (free, or custom for a negotiated deal), allowance overrides, and the day of the month its usage period turns over (--anchor-day). They touch only Postgres and never contact a payments provider. Every write needs --actor and is appended to the org’s history, which show --history prints. They exist for Pug Cloud’s billing; with billing off, a self-hosted instance has no allowance to change. Run pug billing set --help for every flag.
Further reading
- Deployment: run these commands as production processes
- Development: the local workflow and
maketargets that wrap them - Configuration: the environment every command reads