Self-hosting
Configuration reference
Every environment variable a self-hosted Desyn installation reads, what it does, whether it is required and where it is set.
On this page
Desyn is configured entirely through environment variables. They are read in three places, shown as Read by under each variable:
- Convex — the backend functions. They live in the deployment's environment, not in a file: the quickstart keeps them in
deploy/compose/convex.envand pushes them withconvex env set(see Convex); the Convex dashboard can edit them too. - Web app — the Next.js container. Variables starting with
NEXT_PUBLIC_are inlined into the browser bundle at build time, so changing them means rebuilding the image. The quickstart stack derives them from theDESYN_*_URLvalues indeploy/compose/.env. - Worker — the render worker container.
Variables marked secret are credentials. Keep them in your server's environment or a secrets manager, never in git, never in a NEXT_PUBLIC_ variable, and never in logs or screenshots.
Core#
Identity of the installation, its public URLs and the secrets every deployment needs.
DESYN_EDITIONOptionalhosted for desyn.app; any other value (or none) means the self-hosted edition, which unlocks every feature for every account, disables billing and keeps telemetry off by default. Set it explicitly so the web app build and the backend agree. Example: selfhost
SITE_URLRequiredPublic origin of the Desyn site, used for absolute links. On a self-hosted installation this is usually the same origin as APP_URL; when unset it falls back to it. Example: https://design.example.org
APP_URLRequiredPublic origin of the web app. Used for sign-in redirects, email links (verification, magic link, invites) and share links. When unset it falls back to SITE_URL. Example: https://design.example.org
BETTER_AUTH_SECRETRequiredSecretSigns sessions and auth tokens. Use at least 32 random bytes (openssl rand -base64 32). Rotating it signs everyone out.
ADMIN_EMAILSOptionalHosted edition only: comma-separated email addresses with access to the admin console. On a self-hosted installation the first account becomes an administrator and further admins are promoted in the console. Example: ops@example.org,owner@example.org
AUTH_TRUSTED_ORIGINSOptionalAdditional origins, comma-separated, that may call the authentication routes. APP_URL and SITE_URL are always trusted; add others only if you serve the app from more than one origin. Example: https://design.example.com
AUTH_IP_HEADERSOptionalRequest headers, comma-separated and most trusted first, that carry the client IP for per-IP rate limits and session records. The first value found wins (for X-Forwarded-For, its first entry). List only headers your own proxy overwrites: a header clients can set freely lets them bypass the limits. Behind Cloudflare use cf-connecting-ip,x-real-ip. Example: cf-connecting-ip,x-real-ip
AI_KEY_ENCRYPTION_SECRETRequiredSecretEncrypts members' own provider keys (BYOK) at rest (AES-GCM). At least 16 characters; use 32 random bytes. If you lose or change it, stored keys can no longer be decrypted and members must enter them again.
TEMPLATES_PIPELINE_SECRETOptionalSecretToken that lets the template pipeline publish template bundles (templates/admin:publish) without a signed-in administrator. Leave it unset to allow publishing only from an admin session. If you set it, use 32 random bytes and give it only to the machine that runs the pipeline.
MCP_ALLOWED_ORIGINSOptionalExtra browser origins allowed to call the MCP endpoint (/mcp), comma-separated (for example https://claude.ai,https://chat.example.com). The app and site URLs are always allowed; requests without an Origin header (desktop and CLI MCP clients) are unaffected.
Convex#
Where the backend lives. The CLI variables are only needed on the machine that deploys functions.
CONVEX_URLRequiredURL of the Convex deployment's client API (port 3210 on a default self-hosted backend). Server-side code uses this name. Convex provides it to its own functions automatically (from the backend's CONVEX_CLOUD_ORIGIN), so set it only for the web app and the worker. Example: https://convex.example.org
NEXT_PUBLIC_CONVEX_URLRequiredThe same URL as CONVEX_URL, exposed to the browser. It is inlined when the web app is built, so rebuild after changing it. Example: https://convex.example.org
CONVEX_SITE_URLRequiredOrigin that serves Convex HTTP actions (port 3211 on a default self-hosted backend): authentication routes, /api/v1/*, /mcp, /healthz, /og/:id and the Stripe webhook. Convex provides it to its own functions automatically (from the backend's CONVEX_SITE_ORIGIN); set it yourself only for the web app. Example: https://convex-site.example.org
CONVEX_SELF_HOSTED_URLRequired if deploying to a self-hosted backendRead by the Convex CLI (npx convex deploy) to find your self-hosted backend. Only needed where you run the CLI. Example: https://convex.example.org
CONVEX_SELF_HOSTED_ADMIN_KEYRequired if deploying to a self-hosted backendSecretAdmin key of the self-hosted backend, printed by its generate_admin_key.sh script. Grants full control of the deployment; keep it out of the web app's environment.
Object storage#
File bytes (uploads, AI results, exports, snapshots) live in S3 or any S3-compatible store.
S3_BUCKETRequiredBucket that stores uploads, generated assets, exports, thumbnails and design snapshots. Example: desyn
AWS_REGIONRequiredRegion of the bucket (and of SES when MAIL_TRANSPORT=ses). S3-compatible stores that ignore regions still need a value such as us-east-1; Cloudflare R2 uses auto. Example: eu-north-1
AWS_ACCESS_KEY_IDRequiredSecretAccess key for the bucket (and SES). Scope it to the bucket and, if used, ses:SendRawEmail.
AWS_SECRET_ACCESS_KEYRequiredSecretSecret for AWS_ACCESS_KEY_ID.
AWS_SESSION_TOKENOptionalSecretSession token for temporary AWS credentials (STS). Leave unset with long-lived access keys, which is the usual setup.
S3_ENDPOINTOptionalCustom endpoint for S3-compatible storage such as MinIO or Cloudflare R2. Leave unset for AWS S3. Browsers upload directly to presigned URLs on this endpoint, so it must be reachable from members' browsers. Example: https://s3.example.org
S3_FORCE_PATH_STYLEOptionalSet to true for stores that need path-style URLs (endpoint/bucket/key), which includes MinIO. Example: true
S3_PUBLIC_BASE_URLOptionalPublic base URL (for example a CDN) for objects that are safe to serve publicly, such as template thumbnails. When unset, every read uses a short-lived signed URL. Example: https://cdn.example.org
Email#
Verification, magic links, password resets, invites and receipts.
MAIL_TRANSPORTRequiredses (Amazon SES through the AWS credentials above), smtp (any SMTP server via SMTP_URL) or log. With log — also used when the value is unset or unknown — messages are written to the function logs instead of being sent, so nobody can confirm their email. Use it only for local development. Example: smtp
SES_FROMRequiredSender of every outgoing message, for both transports, as Name <address>. The address must be verified in SES, or accepted by your SMTP server. MAIL_FROM is accepted as an alias and wins when both are set. Example: Desyn <design@example.org>
SMTP_URLRequired if MAIL_TRANSPORT=smtpSecretConnection URL for the SMTP transport. Use smtps:// for implicit TLS (port 465) or smtp:// for STARTTLS (port 587). Percent-encode special characters in the password. Example: smtps://user:password@smtp.example.org:465
SES_CONFIGURATION_SETOptionalName of an SES configuration set to send with, for delivery, bounce and complaint tracking in AWS.
AI providers#
Server-side provider keys. All are optional: without them, members can still use their own keys (BYOK) or local models.
OPENROUTER_API_KEYOptionalSecretServer key for text and vision models through OpenRouter. Claude tasks fall back to it when ANTHROPIC_API_KEY is not set or the Anthropic API is unavailable; members can also pick any OpenRouter model per task.
ANTHROPIC_API_KEYOptionalSecretServer key for Claude on the Anthropic API: the default route for the agent, planning, critique, vision, SVG, copy and translation (Claude Sonnet, Opus and Haiku 5.5). Without it those tasks use OpenRouter.
OPENAI_API_KEYOptionalSecretOptional server key for OpenAI models on the OpenAI API. When set, members can pick OpenAI models per task in Settings → AI, billed to platform credits.
GOOGLE_GENERATIVE_AI_API_KEYOptionalSecretOptional server key for Gemini models on the Google Generative AI API. When set, members can pick Gemini models per task in Settings → AI, billed to platform credits.
FAL_AI_API_KEYOptionalSecretServer key for media models on Fal: image generation, background removal, inpaint, outpaint, upscale and video. FAL_KEY is accepted as an alias.
OLLAMA_BASE_URLOptionalDefault base URL offered for the Ollama provider. Members can override it in Settings → AI providers. Example: http://ollama:11434
Render worker#
The Rust worker renders exports, thumbnails and previews for the AI.
RENDER_WORKER_URLRequiredInternal URL of the render worker. Convex actions call it for exports, thumbnails, layout and the previews the agent looks at. Do not expose the worker publicly. Example: http://worker:8080
RENDER_WORKER_SECRETRequiredSecretShared secret sent in the x-desyn-secret header. Set the same value on Convex and on the worker.
FONTS_DIROptionalDirectory of font files the worker loads at start. Design-specific and custom fonts are fetched per request from asset URLs. Example: /usr/share/desyn/fonts
Sign-in providers#
Optional OAuth providers. A provider's button appears only when its credentials are set.
GOOGLE_CLIENT_IDOptionalOAuth client id for Sign in with Google. Register APP_URL/api/auth/callback/google as the authorized redirect URI.
GOOGLE_CLIENT_SECRETRequired if GOOGLE_CLIENT_ID is setSecretOAuth client secret for Sign in with Google.
GITHUB_CLIENT_IDOptionalOAuth app client id for Sign in with GitHub. Set the authorization callback URL to APP_URL/api/auth/callback/github.
GITHUB_CLIENT_SECRETRequired if GITHUB_CLIENT_ID is setSecretOAuth app client secret for Sign in with GitHub.
APPLE_*OptionalReserved for Sign in with Apple. Apple sign-in is not available yet, so leave these unset; the desktop and iOS apps sign in through the browser with email, magic link, Google or GitHub.
NATIVE_REDIRECT_URISOptionalExtra redirect URIs, comma-separated, allowed for the desktop, iOS and CLI sign-in flow. desyn://auth/callback and loopback addresses are always allowed.
Billing (hosted edition)#
Stripe is used only by the hosted edition. Self-hosted installations leave these unset.
STRIPE_SECRET_KEYRequired if DESYN_EDITION=hostedSecretStripe API key for Checkout, the Customer Portal and subscription status.
STRIPE_WEBHOOK_SECRETRequired if DESYN_EDITION=hostedSecretSigning secret of the webhook endpoint at CONVEX_SITE_URL/stripe/webhook.
STRIPE_PRICE_PRO_MONTHLYRequired if DESYN_EDITION=hostedStripe price id of the monthly Pro plan.
STRIPE_PRICE_PRO_YEARLYRequired if DESYN_EDITION=hostedStripe price id of the yearly Pro plan.
Stock media#
Optional stock photo sources for the Photos panel and the agent's search_stock tool.
UNSPLASH_ACCESS_KEYOptionalSecretEnables Unsplash search. Attribution is stored with every photo you place.
PEXELS_API_KEYOptionalSecretEnables Pexels search. Attribution is stored with every photo you place.
Telemetry#
All telemetry is optional and off when unset. With DESYN_EDITION=selfhost it stays off unless you configure it.
NEXT_PUBLIC_POSTHOG_KEYOptionalPostHog project key for product analytics in the browser. Analytics stay off when unset.
NEXT_PUBLIC_POSTHOG_HOSTOptionalPostHog ingestion host. Defaults to the EU region.
POSTHOG_PROJECT_API_KEYOptionalSecretPostHog key for server-side events (for example completed exports and AI runs).
POSTHOG_HOSTOptionalPostHog host for server-side events. Falls back to NEXT_PUBLIC_POSTHOG_HOST.
NEXT_PUBLIC_DATAFAST_WEBSITE_IDOptionalDataFast website id for privacy-friendly traffic analytics.
NEXT_PUBLIC_DATAFAST_DOMAINOptionalDomain registered with DataFast for this installation.
DATAFAST_API_KEYOptionalSecretDataFast API key for server-side goal tracking.
AXIOM_TOKENOptionalSecretAxiom ingest token. When set together with AXIOM_DATASET, structured logs are shipped to Axiom; otherwise they stay in local logs.
AXIOM_DATASETRequired if AXIOM_TOKEN is setAxiom dataset that receives logs. Logs are shipped only when both AXIOM_TOKEN and AXIOM_DATASET are set. Example: desyn
AXIOM_ORG_IDOptionalAxiom organization id (needed for personal tokens).
AXIOM_URLOptionalAxiom API endpoint, for example a regional endpoint.
LOG_LEVELOptionalMinimum level of backend logs: debug, info, warn or error.
Minimal configuration#
A working installation needs only the Core, Convex, Object storage, Email and Render worker groups. Everything else is optional:
- No AI keys: the editor works fully; members can still add their own provider keys or point Desyn at a local model. See AI providers.
- No OAuth providers: members sign in with email and password or magic links.
- No stock keys: the Photos panel shows only uploads and brand assets.
- No telemetry variables: nothing leaves your server. See telemetry.
Generating secrets#
Use a cryptographically random value for each secret, and a different one for each:
openssl rand -hex 32Docs site variables#
If you also host this documentation site (apps/docs), two optional build variables apply:
| Variable | Default | Description |
|---|---|---|
DOCS_BASE_PATH | (empty) | Serve the docs under a sub-path such as /docs. Set at build time. |
DOCS_SITE_URL | https://docs.desyn.app | Canonical origin used for the sitemap and metadata. |