Self-hosting
Configuration
Environment variables for a self-hosted instance.
Set these in .env (or your orchestrator's secrets).
| Variable | Required | Default | Purpose |
|---|
DATABASE_URL | yes | — | Postgres connection string |
POSTGRES_PASSWORD | yes (compose) | — | Password for the bundled Compose Postgres service |
BETTER_AUTH_SECRET | yes (prod) | — | Session secret; must be 32+ random chars |
BETTER_AUTH_URL | no | http://localhost:3000 | Public base URL for auth callbacks and secure-cookie behavior |
KEENPIX_APP_URL | no | BETTER_AUTH_URL | Canonical app URL for generated metadata, docs links, and OG URLs |
KEENPIX_SUPER_ADMIN_EMAIL | yes | — | Email for the bootstrapped super admin account |
KEENPIX_SUPER_ADMIN_PASSWORD | yes | — | Password for the bootstrapped super admin account |
KEENPIX_ADMIN_EMAIL | legacy alias | — | Backward-compatible alias for KEENPIX_SUPER_ADMIN_EMAIL |
KEENPIX_ADMIN_PASSWORD | legacy alias | — | Backward-compatible alias for KEENPIX_SUPER_ADMIN_PASSWORD |
KEENPIX_RUN_MIGRATIONS | no | true in Docker | Docker entrypoint switch for running migrations before start |
KEENPIX_RUN_SEED | no | true in Docker | Docker entrypoint switch for seeding the default org and super admin before start |
KEENPIX_CACHE_DIR | no | ./.keenpix-cache | Where transformed images are cached on disk |
KEENPIX_MODE | no | selfhost | selfhost (default) or cloud. Self-host disables the marketing site, self-signup, and billing |
LOG_LEVEL | no | info | Server log level: fatal, error, warn, info, debug, trace, or silent |
These Vite variables are only needed when the browser bundle must call a non-default
public app or auth URL.
| Variable | Default | Purpose |
|---|
VITE_KEENPIX_PUBLIC_URL | — | Browser-facing app URL |
VITE_KEENPIX_AUTH_URL | — | Backward-compatible auth URL alias |
VITE_BETTER_AUTH_URL | — | Backward-compatible Better Auth URL alias |
Verification, password reset, and teammate invitation emails go through exactly
one provider, selected with EMAIL_PROVIDER. Email is configured entirely
from the environment — there is no in-app email settings screen. Leave
EMAIL_PROVIDER unset to disable email (fine for a single-admin install that
never invites teammates). When it is set, the selected provider's required
variables must be present or the app fails to start.
| Variable | Default | Purpose |
|---|
EMAIL_PROVIDER | — | postmark, resend, or smtp (unset = off) |
| Variable | Default | Purpose |
|---|
POSTMARK_API_KEY | — | Postmark server token (required) |
POSTMARK_FROM | — | Verified sender, e.g. Keenpix <no-reply@…> (required) |
POSTMARK_MESSAGE_STREAM | outbound | Message stream to send on |
| Variable | Default | Purpose |
|---|
RESEND_API_KEY | — | Resend API key (required) |
RESEND_FROM | — | Verified sender, e.g. Keenpix <no-reply@…> (required) |
Point at any SMTP relay (a provider, or a self-hosted Postfix/Mailpit).
| Variable | Default | Purpose |
|---|
SMTP_HOST | — | SMTP server hostname (required) |
SMTP_FROM_EMAIL | — | Sender email address (required) |
SMTP_PORT | 587 | SMTP server port |
SMTP_SECURE | false | true for implicit TLS, false for STARTTLS |
SMTP_USER | — | SMTP username |
SMTP_PASSWORD | — | SMTP password |
SMTP_FROM_NAME | — | Sender display name |
Optional. Wire a Cloudflare API token so keenpix can show real edge cache
hit-rate alongside its origin-shield figures (see edge analytics).
keenpix now persists hourly edge rollups, so edge analytics covers any range and
accumulates history beyond Cloudflare's ~24h adaptive window — coverage grows
forward as the dashboard is used. Manage it from Settings → CDN cache; when
database-managed settings are enabled with a token and zone, they take precedence
over the environment variables below. The token only needs the zone-scoped
Analytics → Read permission. It is stored encrypted at rest with a key derived
from BETTER_AUTH_SECRET, so the encrypted value is per-instance and cannot be
copied between instances — re-enter the token on each instance.
| Variable | Default | Purpose |
|---|
CLOUDFLARE_API_TOKEN | — | Zone-scoped API token with Analytics → Read |
CLOUDFLARE_ZONE_ID | — | 32-character hex zone id for the image hostname |
CLOUDFLARE_HOST | — | Optional. A Cloudflare zone can serve several subdomains and edge analytics only filter by path (/img/*), so set this to the keenpix hostname (e.g. images.example.com) when the zone also hosts other sites that use a /img path. Leave blank to count the whole zone. |
| Variable | Default | Purpose |
|---|
KEENPIX_CACHE_MAX_BYTES | app default: 2147483648 (2GB); Docker/Coolify: 8589934592 (8GB) | Disk cache LRU cap |
KEENPIX_CACHE_STALE_MS | 86400000 (24h) | Serve stale disk entries immediately and refresh them in the background; 0 disables stale refresh |
KEENPIX_MEMORY_CACHE_MAX_BYTES | app default: 67108864 (64MB); Docker/Coolify: 268435456 (256MB) | In-process hot variant LRU cap; 0 disables memory caching |
KEENPIX_MAX_ORIGIN_BYTES | 52428800 (50MB) | Max bytes pulled from an origin before 413 |
KEENPIX_MAX_CONCURRENCY | CPU count | Max simultaneous transforms before queueing |
KEENPIX_MAX_QUEUE | 100 | Queue depth before shedding load with 503 |
KEENPIX_MAX_INPUT_PIXELS | 50000000 | Decode-time pixel ceiling (decompression-bomb guard) |
KEENPIX_MAX_DIMENSION | 4096 | Longest output side when a request gives no w/h |
KEENPIX_ORIGIN_TIMEOUT_MS | 10000 | Per-attempt origin fetch timeout before 504 |