Environment variables for the backend (the server, workers and scheduled jobs). The dashboard is configured separately at build time - see Dashboard.
Copy .env.example from the repo root as your starting point and fill in the values for your environment. Every pug command loads .env from the working directory if present. A variable already set in the process environment wins over the file, which only fills in the rest - so in production you can skip the file and inject these variables directly. A malformed .env stops the command.
cp .env.example .envThe sections below cover the variables an operator sets, grouped by concern. A process stops at startup when a variable it requires is missing. PUG_BILLING_* and PUG_DODO_* configure Pug Cloud’s billing, which is off by default; leave them unset.
Stores
The backend requires four backing services. See Requirements for compatible versions and ports.
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
DATABASE_URL |
Yes | postgres://postgres:postgres@localhost:5433/pug?sslmode=disable |
Postgres connection string (DSN). Standard postgres:// URL including database name and sslmode. |
CLICKHOUSE_URL |
Yes | clickhouse://default:@localhost:9000/pug |
ClickHouse connection string using the native protocol (clickhouse://). |
NATS_URL |
No | nats://localhost:4222 |
NATS server URL. JetStream must be enabled on the server (--jetstream). |
REDIS_URL |
Yes | redis://localhost:6380 |
Dragonfly (Redis-compatible) connection URL. It must report Redis >= 7.0 and persist nothing - see Requirements. Dev default maps host port 6380 to container port 6379. |
NATS
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
NATS_CREDS_FILE |
No | (empty) | Path to a .creds file, for NATS JWT authentication. |
NATS_JWT / NATS_SEED |
No | (empty) | A user JWT and its seed, as an alternative to NATS_CREDS_FILE. Both must be set; a seed on its own is ignored. |
NATS_STREAM_REPLICAS |
No | 1 |
Replica count pug nats migrate applies to every stream. Above 1 needs a named, clustered JetStream; the migrate refuses it otherwise. See NATS JetStream HA. |
NATS_STREAMS_CONFIG |
No | schema/nats/streams.yaml |
Stream definitions, relative to the working directory. Read by pug nats migrate. |
NATS_CONSUMERS_CONFIG |
No | schema/nats/consumers.yaml |
Consumer definitions, relative to the working directory. Read by pug nats migrate and every worker. |
Server
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
PUG_SERVER_PORT |
No | 3000 |
Port the Connect RPC API server listens on. |
PUG_JWT_SECRET_KEY |
Yes | (generate with openssl rand -hex 32) |
Secret used to sign and verify dashboard JWTs. Use a long random string and never reuse it between environments. |
PUG_CORS_ORIGINS |
No | * (.env.example: http://localhost:5173) |
Comma-separated list of origins allowed to call the dashboard API (e.g. https://app.example.com), with no spaces after the commas. Set it to your dashboard’s origin in production: the * default lets any website call the API from a visitor’s browser, and the server warns at startup. |
PUG_CONFIG_FILE |
No | (empty) | Path to a JSON file holding structured settings that don’t fit a flat variable - today, the external sign-in providers (Google and any other OIDC identity provider). Leave empty to offer password and magic-link sign-in only. See Authentication. |
PUG_TURNSTILE_SITE_KEY / PUG_TURNSTILE_SECRET_KEY |
No | (empty) | Put Cloudflare Turnstile in front of password and magic-link sign-in. Set both or neither: one alone stops the server. See Turnstile. |
SSO connections
Org admins can connect their own identity provider under Settings > SSO & domains. Only the server reads these. See Org SSO connections.
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
PUG_SSO_SECRET_KEY |
No | (empty) | Encrypts the connections’ client secrets. Generate with openssl rand -base64 32; a key that isn’t 32 bytes stops the server at startup. Leave empty to turn SSO connections off. Keep it stable: a new key can’t read the stored secrets, so each admin must enter theirs again. |
PUG_SSO_ALLOW_PRIVATE_ISSUERS |
No | false |
Lets connections reach issuers on private addresses, and use the environment’s proxy. Set it for an identity provider on an internal network, or a server that reaches the internet only through a proxy. Leave it off if people outside your company can sign up, so an org admin can’t point a connection at internal services. |
Workers
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
PUG_WORKER_HEALTH_ADDR |
No | :8090 |
Address each worker process serves its /healthz (liveness) and /readyz (readiness) probes on. Set to off to disable. When running several worker binaries on one host, give each a distinct port. pug dev forces this off. See Health checks. |
Scheduled jobs
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
PUG_USAGE_RESCAN_DAYS |
No | 2 |
Trailing window, in days, that pug cron usage recomputes on each pass to catch late events. 0 and negative values fall back to 2. Once a day the pass recomputes the whole current month regardless. |
pug cron purge needs only the stores. Both jobs are described in Scheduled jobs.
Pug sends transactional email: magic links, invites, and a provider test. Configure a provider and the sender identity before users can sign in - new accounts are created through a magic link or an external provider.
The email worker reads these. The server reads them too once PUG_EMAIL_PROVIDER_SECRET_KEY is set.
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
PUG_DASHBOARD_BASE_URL |
Yes | https://app.example.com |
Base URL of the dashboard, used for the links in outgoing emails. No default: the email worker won’t start without it, and pug dev turns email off. |
PUG_EMAIL_FROM |
Yes | noreply@example.com |
From address for all outgoing email. |
PUG_EMAIL_REPLY_TO |
No | (empty) | Reply-To address. Leave empty to omit the header. |
PUG_EMAIL_LOGO_URL |
No | (empty) | URL of a logo image shown in the email header. When empty, a wordmark-only header is rendered. |
PUG_EMAIL_PROVIDER |
No | resend |
Email delivery provider. Supported values: resend or ses. |
PUG_RESEND_API_KEY |
Conditional | - | Resend API key. Required when PUG_EMAIL_PROVIDER=resend. |
PUG_SES_REGION |
Conditional | us-east-1 |
AWS region for SES. Required when PUG_EMAIL_PROVIDER=ses - AWS_REGION doesn’t stand in for it, and the worker stops at startup without it. |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY |
No | - | Static AWS credentials for SES. Optional: SES uses the AWS SDK’s default credential chain, so an IAM role (instance profile, IRSA) or shared config works too. |
AWS_SESSION_TOKEN |
No | (empty) | AWS session token for temporary (STS) credentials. Optional. |
PUG_EMAIL_PROVIDER_SECRET_KEY |
No | (empty) | Encryption key for the email providers org admins configure, stored at rest. Generate with openssl rand -base64 32; it must decode to 32 bytes. Leave empty to turn per-org providers off - the operator-level provider handles all sends. Once it’s set, pug server builds the email stack at startup and stops unless the variables above and a working operator provider are set, and the email worker also needs REDIS_URL. |
You can preview any transactional email without sending it:
pug email preview magic_linkrenders it to HTML on stdout. See the CLI reference.
Telemetry
The backend emits OpenTelemetry traces, metrics, and logs. Export mode is auto-detected once at process start: if an OTLP endpoint variable is set, Pug exports to that collector; otherwise it logs to stdout as plain text (no collector needed).
| Variable | Required? | Default / Example | Description |
|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
No | (empty) | OTLP/gRPC collector endpoint, e.g. http://localhost:4317. When set, Pug exports traces/metrics/logs there. When unset, it logs to stdout. The per-signal OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT variables work too. |
OTEL_EXPORTER_OTLP_INSECURE |
No | true |
Pug exports in plaintext unless this is false. Set it to false for a collector that terminates TLS. |
OTEL_SERVICE_NAME |
No | (empty) | Service name reported in OpenTelemetry spans. Set it per process; when it’s unset with OTLP export on, Pug logs a warning and the telemetry carries no service name. |
Export is gRPC only: OTEL_EXPORTER_OTLP_PROTOCOL is ignored, so a backend that accepts only OTLP over HTTP needs an OpenTelemetry Collector in between. The gRPC exporter’s other standard variables (OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, …) are honored. For a ready-made local collector, see make clickstack.
Demo dataset (optional)
Off by default - these power the public “Pug & Pals” demo and are irrelevant to a normal self-host. PUG_DEMO_ENABLED is the single switch: it gates both the demo worker and the credential-less demo sign-in RPC on the server. Leave it false everywhere unless you’re intentionally hosting a public demo.
| Variable | Default | Description |
|---|---|---|
PUG_DEMO_ENABLED |
false |
Master switch. When true, the demo worker streams synthetic traffic and pug server exposes a read-only demo sign-in. Set on every pod (server + worker) of a demo deployment. |
PUG_DEMO_ADMIN_PASSWORD |
(empty) | When set, pug seed and the demo worker create an admin for the demo org, admin@pug.example, with this password. 16 characters to 72 bytes, with no leading or trailing whitespace; any other value stops pug seed. Without it the demo data has no account that can sign in with a password. |
PUG_DEMO_PEAK_SESSIONS_PER_MIN |
6 |
Peak sessions/min at the busiest hour (~30-50k events/day at 6); off-peak follows a diurnal curve. |
PUG_DEMO_SEED_COUNT |
500000 |
One-time backfill volume when the demo project is empty. |
PUG_DEMO_SEED_BATCH |
10000 |
ClickHouse insert batch size for the backfill. |
Minimal .env example
A minimal configuration for local development. Production deployments need real secrets and a working email provider.
# Stores
DATABASE_URL=postgres://postgres:postgres@localhost:5433/pug?sslmode=disable
CLICKHOUSE_URL=clickhouse://default:@localhost:9000/pug
NATS_URL=nats://localhost:4222
REDIS_URL=redis://localhost:6380
# Server
PUG_SERVER_PORT=3000
PUG_JWT_SECRET_KEY=change_me_use_openssl_rand_hex_32
PUG_CORS_ORIGINS=http://localhost:5173
# Email (Resend)
PUG_DASHBOARD_BASE_URL=http://localhost:5173
PUG_EMAIL_FROM=noreply@example.com
PUG_EMAIL_PROVIDER=resend
PUG_RESEND_API_KEY=re_your_resend_api_keyFurther reading
- Requirements: backing services, ports, and sizing
- Deployment: production build, run, and hardening
- Dashboard: the separate
VITE_*build-time config