Self-Hosting
Run your own LynxPrompt instance with full control over data, features, and branding. A single Docker Compose file gets you started in minutes.
Quick Start
The fastest way to get LynxPrompt running is with the provided docker-compose.selfhost.yml.
Download the compose file and write a .env
curl -O https://raw.githubusercontent.com/GeiserX/LynxPrompt/main/docker-compose.selfhost.yml
(umask 077; printf 'NEXTAUTH_SECRET=%s\nDB_PASSWORD=%s\nADMIN_EMAIL=you@example.com\nAPP_URL=https://lynxprompt.yourcompany.com\n' "$(openssl rand -base64 32)" "$(openssl rand -hex 24)" > .env)The shell runs openssl once and writes the values into .env. Docker Compose does not run commands found inside .env, so never put $(...) there. Sign-in is a link sent by mail, so add the SMTP_* variables too (see Authentication).
Start the services
docker compose -f docker-compose.selfhost.yml up -dOpen the app
Navigate to http://localhost:3000 (or your configured APP_URL). The first user matching ADMIN_EMAIL is automatically promoted to superadmin.
Requirements: Docker Engine 24+ and Docker Compose v2. The default compose file runs one PostgreSQL server with one database per schema, keeps uploads in the uploads volume and exposes port 3000.
Environment Variables
All configuration is done through environment variables. NEXTAUTH_SECRET and the database connection are required; the self-host compose file also needs DB_PASSWORD.
Core
| Variable | Default | Description |
|---|---|---|
NEXTAUTH_SECRET | — | Session encryption key. Required. Generate with openssl rand -base64 32 |
APP_URL | http://localhost:3000 | Public URL of the instance (used for callbacks, emails, and CLI) |
NEXTAUTH_URL | same as APP_URL | NextAuth callback URL. Usually the same as APP_URL |
SUPERADMIN_EMAIL | — | Email auto-promoted to superadmin on first sign-in |
NODE_ENV | production | Set to production for self-hosted deployments |
TRUSTED_PROXY | 1 | How many reverse proxies sit in front of the app; rate limits and download counts key on the client address it finds. cloudflare reads CF-Connecting-IP instead; use it only when the app cannot be reached except through Cloudflare |
Database
| Variable | Default | Description |
|---|---|---|
DATABASE_URL_APP | — | Main application database (blueprints, marketplace) |
DATABASE_URL_USERS | — | User accounts, sessions, authentication |
DATABASE_URL_BLOG | — | Blog content (when ENABLE_BLOG is on) |
DATABASE_URL_SUPPORT | — | Support forum data (when ENABLE_SUPPORT_FORUM is on) |
DB_SSL | false | Set to true to make the app's own database connections use TLS. The migrations at start connect with the connection string as written |
DB_SSL_REJECT_UNAUTHORIZED | true | With DB_SSL on, false accepts a server certificate that does not verify, such as a self-signed one |
Authentication
| Variable | Default | Description |
|---|---|---|
ENABLE_EMAIL_AUTH | true | Magic link email sign-in (requires SMTP) |
ENABLE_PASSKEYS | true | WebAuthn passkey sign-in |
ENABLE_GITHUB_OAUTH | false | GitHub OAuth (requires GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET) |
ENABLE_GOOGLE_OAUTH | false | Google OAuth (requires GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET) |
ENABLE_SSO | false | Enterprise SSO (OpenID Connect), configured per team. Needs SSO_ENCRYPTION_KEY |
SSO_ENCRYPTION_KEY | — | 64 hex characters (openssl rand -hex 32). Encrypts the SSO client secrets at rest; without it a team cannot save an SSO configuration. Changing it makes the stored secrets unreadable |
SSO_ALLOW_PRIVATE_IDP | false | Set to true to let a team's IdP live on a private or loopback address and use plain http. Leave it off on an instance strangers can sign up to |
ENABLE_USER_REGISTRATION | true | Allow new user registration. Set to false for invite-only |
ENABLE_TURNSTILE | false | Cloudflare Turnstile on the sign-in forms. Needs both keys below |
NEXT_PUBLIC_TURNSTILE_SITE_KEY | — | Turnstile site key, read when the container starts |
TURNSTILE_SECRET_KEY | — | Turnstile secret key. With ENABLE_TURNSTILE on and no secret, the server skips the check |
AI Features
| Variable | Default | Description |
|---|---|---|
ENABLE_AI | false | Enable AI editing & wizard assistant |
ANTHROPIC_API_KEY | — | Anthropic API key (required when ENABLE_AI is true) |
AI_MODEL | claude-haiku-5-5 | Anthropic model to use for AI features |
Federation
| Variable | Default | Description |
|---|---|---|
ENABLE_FEDERATION | false | Set to true to register the instance's domain with the federation registry at start and every six hours, and serve /.well-known/lynxprompt.json. Needs an APP_URL on https with a public domain name. Left off, the instance makes no call to the registry |
FEDERATION_REGISTRY_URL | https://lynxprompt.com | Optional. URL of the federation registry. Only set this if you run your own registry; omit to use the default public network |
Branding & Content
| Variable | Default | Description |
|---|---|---|
APP_NAME | LynxPrompt | Application name shown in UI, emails, and metadata |
APP_LOGO_URL | — | Logo used in sign-in mail and search-engine metadata (the header keeps the default logo) |
CONTACT_EMAIL | — | Where the contact form sends, also shown in page metadata. The contact form needs SMTP_HOST, SMTP_USER and SMTP_PASSWORD |
STATUS_PAGE_URL | — | Link to your status page (e.g., Upptime, Kuma) |
ENABLE_BLOG | false | Enable the built-in blog |
UPLOAD_DIR | /data/uploads/blog | Where blog images go; avatars and team logos go one level up. The self-host compose file mounts a volume at /data/uploads |
ENABLE_SUPPORT_FORUM | false | Enable the support forum |
UMAMI_SCRIPT_URL | — | Umami analytics script URL. Loaded only when NEXT_PUBLIC_UMAMI_WEBSITE_ID is set too |
NEXT_PUBLIC_UMAMI_WEBSITE_ID | — | Umami website id |
Authentication Configuration
Out of the box, LynxPrompt supports passkeys and email magic links. Add OAuth providers or lock down registration as needed.
Passkeys (default: on)
WebAuthn passkeys work immediately with no extra configuration. Requires HTTPS in production for browser WebAuthn APIs.
Email Magic Links (default: on)
Requires an SMTP server. Set SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD and SMTP_FROM in your environment. Without SMTP, disable with ENABLE_EMAIL_AUTH=false.
GitHub OAuth
Set ENABLE_GITHUB_OAUTH=true, GITHUB_CLIENT_ID, and GITHUB_CLIENT_SECRET. Create an OAuth App at GitHub → Settings → Developer settings → OAuth Apps. Set the callback URL to {APP_URL}/api/auth/callback/github.
Google OAuth
Set ENABLE_GOOGLE_OAUTH=true, GOOGLE_CLIENT_ID, and GOOGLE_CLIENT_SECRET. Configure in Google Cloud Console with redirect URI {APP_URL}/api/auth/callback/google.
Invite-Only Mode
Set ENABLE_USER_REGISTRATION=false to prevent new sign-ups. Existing users can still sign in, and a team admin can bring someone new in by inviting their address to a team.
AI Features Setup
AI-powered blueprint editing and wizard assistance are opt-in.
Get an Anthropic API Key
Sign up at console.anthropic.com and create an API key.
Set environment variables
ENABLE_AI=true
ANTHROPIC_API_KEY=sk-ant-...
AI_MODEL=claude-haiku-5-5 # optionalRestart the container
AI buttons will appear automatically in the UI for all users.
Cost note: AI requests are billed by Anthropic to your API key. LynxPrompt does not add any surcharge. Monitor usage at the Anthropic dashboard.
Custom Branding
White-label LynxPrompt for your organization.
APP_NAME=MyCompany Prompts
APP_LOGO_URL=https://cdn.mycompany.com/logo.svg
CONTACT_EMAIL=support@mycompany.com
STATUS_PAGE_URL=https://status.mycompany.comThe app name is used throughout the UI, in page titles, email templates, and OpenGraph metadata. The logo is used in the sign-in mail and in search-engine metadata; the header keeps the default LynxPrompt logo.
Database Architecture
LynxPrompt uses four Prisma clients, each with its own connection string. This allows flexible deployment topologies.
One server, one database per schema (default)
Point the four DATABASE_URL_* variables at four databases on the same PostgreSQL server. This is what docker-compose.selfhost.yml does. The first start creates the users database; the blog and support databases are only created and migrated while ENABLE_BLOG or ENABLE_SUPPORT_FORUM is on, so an instance without those features runs on two databases.
DATABASE_URL_APP=postgresql://.../lynxprompt
DATABASE_URL_USERS=postgresql://.../lynxprompt_users
DATABASE_URL_BLOG=postgresql://.../lynxprompt_blog
DATABASE_URL_SUPPORT=postgresql://.../lynxprompt_supportNever point two of them at the same database. At every start the entrypoint runs prisma db push once per schema, and each push drops the tables it does not know, so the schemas would delete each other's tables.
Multi-Database
For larger deployments, split databases by concern. Each client connects to a separate database or server, allowing independent scaling and backup strategies.
DATABASE_URL_APP=postgresql://app-db/lynx
DATABASE_URL_USERS=postgresql://auth-db/users
DATABASE_URL_BLOG=postgresql://blog-db/blog
DATABASE_URL_SUPPORT=postgresql://sup-db/forumCLI for Self-Hosted Instances
The LynxPrompt CLI works with self-hosted instances. After installing the CLI, point it to your instance:
lynxp config set-url https://lynxprompt.yourcompany.com
lynxp loginThis stores the API URL locally. All subsequent CLI commands (push, pull, sync) will target your self-hosted instance instead of the public service.
Reverse Proxy & WAF
LynxPrompt works behind any reverse proxy (Nginx, Caddy, Traefik). However, if you use Cloudflare as a reverse proxy, there is an important caveat for the API.
Cloudflare WAF & Blueprint Content
Cloudflare's platform-level security layer inspects request bodies and blocks patterns that resemble OS command injection (e.g. ssh user@host, /etc/passwd). Since blueprints often contain infrastructure code, shell commands, and server configurations, these legitimate API calls can trigger HTTP 403 errors.
This protection is applied at Cloudflare's edge before configurable WAF rulesets run, and cannot be disabled via WAF exceptions or skip rules on any plan.
Recommended workaround:
Create a separate DNS record for API traffic that bypasses Cloudflare's proxy (DNS-only / grey cloud):
# Cloudflare DNS records:
#
# lynxprompt.yourcompany.com → A your-ip (Proxied, orange cloud)
# api.lynxprompt.yourcompany.com → A your-ip (DNS only, grey cloud)
#
# Then configure your reverse proxy to serve both domains,
# pointing to the same LynxPrompt backend.
#
# The CLI defaults to https://lynxprompt.com for the
# public instance. For self-hosted, point the CLI:
lynxp config set-url https://api.lynxprompt.yourcompany.comThe main domain keeps Cloudflare's CDN, DDoS protection, and caching for the web UI. The API subdomain routes directly to your origin, where your reverse proxy handles TLS and rate limiting.
Not using Cloudflare? No action needed. Reverse proxies like Nginx, Caddy, and Traefik do not inspect request bodies by default and work out of the box.
Federation
Federation lists independent LynxPrompt instances in a shared directory, so people can find them. Blueprints stay on the instance that holds them: browsing other instances' blueprints from your own UI is not built yet.
Federation is off until you turn it on
ENABLE_FEDERATION=true # the default is falseTurned on, the instance joins the default public network at https://lynxprompt.com. Its APP_URL must be https on a public domain name, since the registry checks it from the internet. Only set FEDERATION_REGISTRY_URL if you are running your own federation registry.
What it sends
At start and every six hours the instance sends its domain (the host of APP_URL) to the registry. /.well-known/lynxprompt.json answers with its name, version and number of public blueprints.
Find other instances
The /federation page lists the registered instances. Each one also lists the name, description and author of its public blueprints at /api/v1/federation/blueprints.
Note: Federation is optional. Without ENABLE_FEDERATION=true your instance works as a fully standalone platform with its own local blueprint library and makes no call to the registry.
Health Check
LynxPrompt exposes a health endpoint for monitoring and orchestration:
GET /api/health
# Healthy response (200):
{"status":"ok","db":"connected","version":"X.Y.Z","revision":"<commit>"}
# Unhealthy response (503):
{"status":"error","db":"disconnected","version":"X.Y.Z","revision":"<commit>"}Use this endpoint in Docker health checks, Kubernetes liveness probes, or external monitoring tools like Uptime Kuma.
# Docker Compose healthcheck example
healthcheck:
# The image has no curl; Node is always there.
test: ["CMD", "node", "-e", "fetch('http://localhost:3000/api/health').then(r => process.exit(r.ok ? 0 : 1), () => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3