Skip to content

Development

Run Pug locally from source: dev infrastructure, code generation, tests and the demo seeder.

Run the whole Pug stack on your machine (backend, backing services, and optionally the dashboard) from clone to a working server you can point an SDK at.

Install the prerequisites from Requirements first: Docker, a Go >= 1.27.2 toolchain, and (if you want the dashboard) Bun.

1. Clone and configure

git clone https://github.com/pug-sh/pug.git
cd pug
cp .env.example .env

.env.example is pre-filled with defaults that match the dev Docker Compose (Postgres on 5433, ClickHouse on 9000, NATS on 4222, Dragonfly on 6380). Open .env and set at least:

PUG_JWT_SECRET_KEY=<any-long-random-string>
PUG_DEMO_ADMIN_PASSWORD=<16-or-more-characters>   # so you can sign in - see step 4

Email is configured for Resend with a placeholder key, so magic links don’t arrive until you put a real PUG_RESEND_API_KEY there. New accounts are created only through a magic link or an external provider, so without one the demo admin from step 4 is how you sign in.

Full variable reference: Configuration.

2. Start infrastructure

make infra-up      # `make infra` is an alias

This runs docker compose -f infra/dev/docker-compose.yaml up -d, starting the four backing services on one shared bridge network, each with a health check and a named volume for persistence:

Service Image Host port(s) Role
PostgreSQL postgres:18 5433 -> 5432 Auth, orgs, projects, config
NATS nats:2.14-alpine 4222 / 6222 / 8222 JetStream (started with --jetstream); monitoring on 8222
ClickHouse clickhouse-server:26.5 8123 / 9000 / 9009 Event storage (nofile raised to 262144)
Dragonfly dragonfly:v1.38.1 6380 -> 6379 Redis-compatible cache

The compose file is for local development only - not a production topology. In production you run these as managed services or your own cluster. Dragonfly’s volume in particular is dev-only: production must keep it on no disk at all (Requirements). Stop the containers with make infra-down.

Target Compose action
make infra-up Start the four services detached
make infra-up-fg Start them in the foreground (logs to your terminal)
make infra-down Stop and remove them

3. Run migrations

Run once before the first boot and again after each upgrade. Order doesn’t matter - they’re safe to re-run.

go run ./cmd/pug postgres migrate
go run ./cmd/pug nats migrate
go run ./cmd/pug clickhouse migrate

Each reads its connection details from .env (DATABASE_URL, NATS_URL, CLICKHOUSE_URL). There is no make migrate target - migrations are pug commands. Use go run ./cmd/pug ... here, or ./bin/pug ... after make build.

4. Seed test data (optional)

Populate the demo project with synthetic events and profiles so there’s something to query right away:

go run ./cmd/pug seed --count 10000

pug seed resets Postgres and ClickHouse, then generates events and seeds profiles for the users behind them. The default is 500,000 events - pass --count to keep it light on a laptop. See the CLI reference for all flags (--batch, --no-reset).

With PUG_DEMO_ADMIN_PASSWORD set (step 1), the seed also creates admin@pug.example, an admin of the demo org: sign in to the dashboard with that address and password to browse the data. The demo’s own accounts are read-only and have no password.

There is a single pug seed command - not per-store postgres seed / clickhouse seed. It handles both stores itself.

5. Start the dev server

go run ./cmd/pug dev      # `pug start` is an alias

pug dev starts the API server and all workers together in one process. On boot it prints the server URL and the active worker list; the server listens on :3000 (override with PUG_SERVER_PORT). Press Ctrl+C to stop everything cleanly. See dev in the CLI reference.

Compiled alternative

To run the compiled binary (or reproduce how production binaries are built):

make build
./bin/pug dev

make build compiles bin/pug plus a standalone binary for the server, each worker, each migration runner and each scheduled job - see Deployment > Build.

pug dev doesn’t run the scheduled jobs; its boot output lists them. Run go run ./cmd/pug cron purge or cron usage for one pass when you need it.

6. Run the dashboard (optional)

To use the UI locally, clone and run pug-sh/app next to the backend checkout:

cd ..
git clone https://github.com/pug-sh/app.git
cd app
cp .env.example .env
bun install
bun run dev      # http://localhost:5173

Its .env.example sets VITE_API_BASE_URL to http://localhost:3000, which matches your local pug dev; without the copy the app throws on load. The backend’s .env.example sets PUG_CORS_ORIGINS to http://localhost:5173, which allows the Vite dev server. Sign in as the demo admin from step 4, or set PUG_DEMO_ENABLED=true in the backend’s .env, restart pug dev, and open http://localhost:5173/demo for a read-only view.

7. Point an SDK at localhost

Set the endpoint to http://localhost:3000 when initializing an SDK:

import { init } from '@pug-sh/browser'

init('YOUR_PROJECT_ID', {
  apiKey: 'pub_YOUR_PUBLIC_KEY',
  endpoint: 'http://localhost:3000',
})

The default endpoint is Pug Cloud, so this override is required to hit your local server. Full per-SDK guidance: Connect your SDKs.

Observability (ClickStack)

By default the backend logs to stdout as text - no collector required. To view traces, metrics, and logs locally, start the optional ClickStack overlay, which adds a HyperDX OTLP collector and UI on top of the dev services:

make clickstack        # infra + HyperDX collector (OTLP :4317/:4318) + UI (:3001)

Then set the OTLP endpoint in .env and restart pug dev:

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317

Pug auto-detects the endpoint at startup and exports there instead of logging to stdout; open the HyperDX UI at http://localhost:3001. make clickstack-down stops the whole dev stack, the four backing services included. The overlay is infra/dev/docker-compose.clickstack.yaml, layered onto the base compose file.

Make targets

Everyday

Target What it does
make infra-up / make infra-down Start / stop the dev backing services
make clickstack / make clickstack-down Start the dev services plus the OTLP collector + HyperDX UI / stop all of it
make build Compile all binaries into bin/
make test Run the Go test suite with the race detector
make cover Run tests and write coverage.out
make lint Lint Go code (golangci-lint)
make psql Open a psql shell in the running Postgres container
make chql Open a clickhouse-client shell in the running ClickHouse container

Code generation (when editing backend source)

Pug generates code from three sources; regenerate after editing the corresponding inputs, then commit the output:

Target Regenerates Run after editing
make sqlc Type-safe Go query code (into internal/gen/repo) SQL query files under schema/postgres
make rpc Connect RPC / protobuf Go (lints protos first) .proto files under proto/
make templ Compiled Go for the transactional email templates templ email templates

The dashboard’s TypeScript client is generated in its own repo, with bun run generate in pug-sh/app.

Common issues

Symptom Fix
make infra-up fails Confirm Docker is running; check for port conflicts on 5433, 4222, 6222, 8222, 8123, 9000, 9009, 6380
Migrations fail Run make infra-up first and wait for the containers to report healthy
pug dev exits immediately Check .env exists, PUG_JWT_SECRET_KEY is set, and DATABASE_URL / NATS_URL / CLICKHOUSE_URL / REDIS_URL are reachable
Events not arriving Check the pug dev log for an events-worker error, and that you ran pug nats migrate. The boot banner lists the workers before they start, so it doesn’t prove one is running
SDK requests fail with a network error Set the SDK endpoint to http://localhost:3000 explicitly - the default is Pug Cloud, not your local server
Dashboard can’t sign in Ensure the backend is running and its PUG_CORS_ORIGINS includes http://localhost:5173. With the placeholder Resend key no magic link arrives - sign in as the demo admin

Further reading

Navigation

Type to search...

↑↓ navigate↵ selectEsc close