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.

1

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

2

Start the services

docker compose -f docker-compose.selfhost.yml up -d
3

Open 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

VariableDefaultDescription
NEXTAUTH_SECRET—Session encryption key. Required. Generate with openssl rand -base64 32
APP_URLhttp://localhost:3000Public URL of the instance (used for callbacks, emails, and CLI)
NEXTAUTH_URLsame as APP_URLNextAuth callback URL. Usually the same as APP_URL
SUPERADMIN_EMAIL—Email auto-promoted to superadmin on first sign-in
NODE_ENVproductionSet to production for self-hosted deployments
TRUSTED_PROXY1How 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

VariableDefaultDescription
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_SSLfalseSet 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_UNAUTHORIZEDtrueWith DB_SSL on, false accepts a server certificate that does not verify, such as a self-signed one

Authentication

VariableDefaultDescription
ENABLE_EMAIL_AUTHtrueMagic link email sign-in (requires SMTP)
ENABLE_PASSKEYStrueWebAuthn passkey sign-in
ENABLE_GITHUB_OAUTHfalseGitHub OAuth (requires GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET)
ENABLE_GOOGLE_OAUTHfalseGoogle OAuth (requires GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET)
ENABLE_SSOfalseEnterprise 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_IDPfalseSet 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_REGISTRATIONtrueAllow new user registration. Set to false for invite-only
ENABLE_TURNSTILEfalseCloudflare 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

VariableDefaultDescription
ENABLE_AIfalseEnable AI editing & wizard assistant
ANTHROPIC_API_KEY—Anthropic API key (required when ENABLE_AI is true)
AI_MODELclaude-haiku-5-5Anthropic model to use for AI features

Federation

VariableDefaultDescription
ENABLE_FEDERATIONfalseSet 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_URLhttps://lynxprompt.comOptional. URL of the federation registry. Only set this if you run your own registry; omit to use the default public network

Branding & Content

VariableDefaultDescription
APP_NAMELynxPromptApplication 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_BLOGfalseEnable the built-in blog
UPLOAD_DIR/data/uploads/blogWhere blog images go; avatars and team logos go one level up. The self-host compose file mounts a volume at /data/uploads
ENABLE_SUPPORT_FORUMfalseEnable 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.

1

Get an Anthropic API Key

Sign up at console.anthropic.com and create an API key.

2

Set environment variables

ENABLE_AI=true
ANTHROPIC_API_KEY=sk-ant-...
AI_MODEL=claude-haiku-5-5  # optional
3

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

The 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_support

Never 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/forum

CLI 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 login

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

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

1

Federation is off until you turn it on

ENABLE_FEDERATION=true   # the default is false

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

2

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.

3

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

Next Steps