---
title: "Development"
description: "Run Pug locally from source: dev infrastructure, code generation, tests and the demo seeder."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.pug.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Development

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](/self-hosting/requirements) first: [Docker](https://www.docker.com/products/docker-desktop/), a **Go ≥ 1.26** toolchain, and — if you want the dashboard — **Bun**.

## 1. Clone and configure

```bash
git clone git@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:

```bash
PUG_JWT_SECRET_KEY=<any-long-random-string>
```

Full variable reference: [Configuration](/self-hosting/configuration).

## 2. Start infrastructure

```bash
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 — each with a health check, a named volume for persistence, and its own bridge network:

| 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. 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.

```bash
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:

```bash
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](/self-hosting/cli#seed) for all flags (`--batch`, `--no-reset`).

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

## 5. Start the dev server

```bash
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 [CLI reference → `dev`](/self-hosting/cli#dev).

### Compiled alternative

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

```bash
make build
./bin/pug dev
```

`make build` compiles `bin/pug` plus a standalone binary per worker and migration runner — see [Deployment → Build](/self-hosting/deployment#1-build).

## 6. Run the dashboard (optional)

To use the UI locally, clone and run [`pug-sh/app`](/self-hosting/dashboard) alongside the backend:

```bash
cd ../app        # separate repo
bun install
bun run dev      # http://localhost:5173
```

Its default `VITE_API_BASE_URL` is `http://localhost:3000`, which already matches your local `pug dev` — no extra config. The backend's default `PUG_CORS_ORIGINS` (`http://localhost:5173`) already allows the Vite dev server, so sign-in works out of the box.

## 7. Point an SDK at localhost

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

```ts
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](/self-hosting/connect-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:

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

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

```env
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`. Stop it with `make clickstack-down`. 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` | Add / remove the local OTLP collector + HyperDX UI |
| `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 |
| `make gen-ts` | TypeScript types from the protos | `.proto` files (for the dashboard/SDKs) |

## Common issues

| Symptom | Fix |
|---------|-----|
| `make infra-up` fails | Confirm Docker is running; check for port conflicts on `5433`, `4222`, `8123`, `6380` |
| Migrations fail | Run `make infra-up` first and wait for the containers to report healthy |
| `pug dev` exits immediately | Check `.env` exists and `DATABASE_URL` / `NATS_URL` / `CLICKHOUSE_URL` are reachable |
| Events not arriving | Confirm the events worker started (it's listed in the `pug dev` output) |
| 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` |

## Further reading

- [CLI reference](/self-hosting/cli) — every `pug` command and flag
- [Dashboard](/self-hosting/dashboard) — build and configure the UI
- [Configuration](/self-hosting/configuration) — all environment variables
- [Deployment](/self-hosting/deployment) — take this to production

Source: https://docs.pug.sh/self-hosting/development/index.mdx
