Configuration
Every Repro setting is a single environment variable. The bundled compose.yaml reads them from .env in the same directory. This page is the exhaustive reference; grab .env.example for a well-commented template.
Required
You need exactly four values to boot:
| Variable | Description | Generate with |
|---|---|---|
POSTGRES_PASSWORD | Postgres password. Kept internal to the Docker network. | openssl rand -hex 32 |
BETTER_AUTH_URL | Base URL your dashboard is served at. Must match the origin visitors use (session cookies). | — |
BETTER_AUTH_SECRET | Session-cookie signing secret. | openssl rand -hex 32 |
ATTACHMENT_URL_SECRET | Signing key for time-limited attachment download URLs. | openssl rand -hex 32 |
Without these four the stack refuses to start (compose refuses interpolation; the dashboard refuses to boot).
Compose overrides
Optional knobs the bundled compose reads:
| Variable | Default | Description |
|---|---|---|
REPRO_VERSION | latest | Pin the image tag. Recommended in prod so you control when you upgrade. |
PORT | 3000 | Host port the dashboard binds to. Container internal port is always 3000. |
POSTGRES_USER | repro | Postgres user name. |
POSTGRES_DB | repro | Postgres database name. |
The compose file wires DATABASE_URL internally to the postgres service — don't set it yourself when using compose.
Storage
See Storage for per-provider endpoints.
| Variable | Default | Description |
|---|---|---|
STORAGE_DRIVER | local | local (Docker volume) or s3 (any S3-compatible endpoint). |
STORAGE_LOCAL_ROOT | /data/attachments | Path inside the container. Compose mounts the volume here. |
S3_BUCKET | — | Bucket name (s3 only). |
S3_REGION | — | Region (s3 only). auto for R2; any string for MinIO. |
S3_ACCESS_KEY_ID | — | Access key (s3 only). |
S3_SECRET_ACCESS_KEY | — | Secret (s3 only). |
S3_ENDPOINT | — | Leave empty for AWS; per-provider URL otherwise. |
S3_VIRTUAL_HOSTED | false | true for AWS S3 (virtual-hosted addressing); false elsewhere. |
Email
Magic-link sign-in + invite delivery.
| Variable | Default | Description |
|---|---|---|
MAIL_PROVIDER | console | console (logs the link to stdout), smtp (real email), ethereal (nodemailer preview). |
SMTP_HOST | — | SMTP server hostname (when MAIL_PROVIDER=smtp). |
SMTP_PORT | 587 | SMTP port. |
SMTP_USER | — | SMTP auth user. |
SMTP_PASS | — | SMTP auth password. |
SMTP_FROM | Repro <no-reply@localhost> | From: header on sent mail. |
console is the right default for the first boot — grab the magic-link URL from docker compose logs dashboard | grep link: and paste it into your browser. Switch to smtp once you want real users to receive email.
OAuth sign-in
Buttons are hidden on the sign-in page when the secrets are blank. Leave the fields empty to disable a provider.
| Variable | Description |
|---|---|
GITHUB_CLIENT_ID | GitHub OAuth App client id. |
GITHUB_CLIENT_SECRET | GitHub OAuth App client secret. |
GOOGLE_CLIENT_ID | Google OAuth client id. |
GOOGLE_CLIENT_SECRET | Google OAuth client secret. |
Callback URLs you'll need in the OAuth app configuration:
<BETTER_AUTH_URL>/api/auth/callback/github<BETTER_AUTH_URL>/api/auth/callback/google
GitHub Issues sync
Uses a GitHub App (not OAuth). See Integrations → GitHub for the walkthrough.
| Variable | Default | Description |
|---|---|---|
GITHUB_APP_ID | — | Numeric App ID from the App's settings page. |
GITHUB_APP_PRIVATE_KEY | — | Literal PEM contents (newlines as \n) or an absolute path to a .pem. |
GITHUB_APP_WEBHOOK_SECRET | — | Whatever you pasted into the App's Webhook secret field. |
GITHUB_APP_SLUG | repro | Slug from the App's public URL (e.g. https://github.com/apps/<slug>). |
GITHUB_WEBHOOK_MAX_BYTES | 1048576 | Max webhook body before HMAC validation. Keep at the default. |
Database connection + pool
DATABASE_URL is set by the compose file to the internal postgres service. If you're running the dashboard outside compose (pointing at a managed Postgres), set it yourself to postgres://user:pass@host:5432/dbname.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | — | Postgres connection string. Required if not using the bundled compose. |
DB_POOL_MAX | 10 | Max concurrent connections per dashboard worker. |
DB_STATEMENT_TIMEOUT_MS | 30000 | Kills any single query exceeding this many ms. |
DB_IDLE_TX_TIMEOUT_MS | 10000 | Kills connections held open by a leaked BEGIN. |
Intake limits
Tune the SDK intake path. Defaults are safe — only change if you have a specific reason.
| Variable | Default | Description |
|---|---|---|
INTAKE_RATE_PER_KEY | 60 | Reports / minute / project. |
INTAKE_RATE_PER_IP | 20 | Reports / minute / IP. |
INTAKE_RATE_PER_KEY_ANON | 10 | Stricter bucket for anonymous submissions (no reporter.userId). |
INTAKE_MAX_BYTES | 5242880 | Max total multipart payload (5 MB). |
INTAKE_REQUIRE_DWELL | true | Reject submissions that omit _dwellMs. Set false during a rolling SDK upgrade only. |
INTAKE_MIN_DWELL_MS | 1500 | Minimum ms between widget-open and submit (anti-bot). |
INTAKE_REPLAY_MAX_BYTES | 1048576 | Max gzipped bytes per replay attachment. |
REPLAY_FEATURE_ENABLED | true | Kill-switch for session replay across the whole install. |
TRUST_XFF | false | Trust X-Forwarded-For. Only set true when behind a trusted reverse proxy — spoofable. |
Auth rate limits
Protects /api/auth/sign-in and /api/auth/magic-link/verify from credential-stuffing / enumeration.
| Variable | Default | Description |
|---|---|---|
AUTH_RATE_PER_IP_PER_15MIN | 5 | Max attempts per IP per 15-minute window. |
AUTH_RATE_LIMIT_ENABLED | on in prod; off in dev/test | Explicit override: true force-on, false force-off. |
RATE_LIMIT_STORE | memory | memory (per-worker, fine for single replica) or postgres (shared across replicas). |
Invites
| Variable | Default | Description |
|---|---|---|
INVITE_RATE_PER_ADMIN | 5 | Invite emails / minute / admin. Catches runaway loops before SMTP quota does. |
Runtime
| Variable | Default | Description |
|---|---|---|
NODE_ENV | production | Set by the image. Don't override in .env. |
NITRO_HOST | 0.0.0.0 | Bind address. Don't override. |
NITRO_PORT | 3000 | Container internal port. |
Validating your config
Compose can dry-run interpolation without starting anything:
docker compose configAnything missing or malformed surfaces immediately.