Open: Self Host

Environment variables

Every environment variable the Open branch reads and where to set it, plus the access control, reverse proxy and HTTPS an exposed instance needs.

Browse documentation

You run the Open branch. Everything here applies to the open-source main branch on a machine you control: the same product the hosted site runs, with the pieces hosted operates for you left in your hands. The code lives on GitHub - if Open serves you, star the repo.

Every variable is optional. The app starts with an empty environment, and each person configures their own AI services inside it. Each variable below adds a capability or moves a default. One AI service key is the whole minimum for an instance that works on first load.

Precedence

a person's own setting  ->  your environment variable  ->  the built-in default

That order holds at every point where a credential or an address is read. One deliberate inversion: when a Bedrock request falls through to your server credential, the region comes from BEDROCK_REGION. The region the browser sent is ignored.

AI service keys

Set the key and the service appears, already configured, for everyone who opens the instance.

VariableWhat it doesDefault
ANTHROPIC_API_KEY · ANTHROPIC_API_HOSTAnthropic credential; host overrideapi.anthropic.com
OPENAI_API_KEY · OPENAI_API_HOST · OPENAI_API_ORG_IDOpenAI credential; host override; organization idapi.openai.com
GEMINI_API_KEYGoogle Gemini credential-
DEEPSEEK_API_KEY · GROQ_API_KEY · MISTRAL_API_KEY · MOONSHOT_API_KEY · OPENROUTER_API_KEY · PERPLEXITY_API_KEY · TOGETHERAI_API_KEY · XAI_API_KEYOne credential each-
ALIBABA_API_KEY · ALIBABA_API_HOSTAlibaba credential; host overridehttps://dashscope-intl.aliyuncs.com/compatible-mode
NVIDIANIM_API_KEY · NVIDIANIM_API_HOSTNVIDIA NIM credential; host overridehttps://integrate.api.nvidia.com
CEREBRAS_API_KEYCerebras credential - reports nothing-
SAKANA_API_KEY · SAKANA_API_HOSTSakana credential; host override; reports nothing toohttps://api.sakana.ai
OLLAMA_API_HOSTAddress of an Ollama server the app's server can reachhttp://127.0.0.1:11434
LOCALAI_API_HOST · LOCALAI_API_KEYAddress of a LocalAI server; optional credentialhttp://127.0.0.1:8080
AZURE_OPENAI_API_ENDPOINT · AZURE_OPENAI_API_KEYAzure endpoint and credential - both required together-
AZURE_OPENAI_API_VERSION · AZURE_DEPLOYMENTS_API_VERSION · AZURE_OPENAI_DISABLE_V1Azure API versions; true turns the v1 API off2025-04-01-preview · 2023-03-15-preview · unset
BEDROCK_BEARER_TOKEN or BEDROCK_ACCESS_KEY_ID + BEDROCK_SECRET_ACCESS_KEYAWS Bedrock credential, either form-
BEDROCK_SESSION_TOKEN · BEDROCK_REGIONTemporary-credential token; region, server-side only- · us-east-1

Azure's v1 API is the default. It calls /openai/v1/responses with no deployment id; disabling it sends every model back through /openai/deployments/<name>/.

Cohere, LM Studio and Z.ai take no server-side variable: those are added per person in the app.

Services

VariableWhat it doesDefault
PUPPETEER_WSS_ENDPOINTRemote browser for page fetching. ws:// or wss:// only-
GOOGLE_CLOUD_API_KEY · GOOGLE_CSE_IDCustom Search credentials - both required, or neither works-
ELEVENLABS_API_KEY · ELEVENLABS_API_HOST · ELEVENLABS_VOICE_IDElevenLabs credential, host, voice - unread today-
POSTGRES_PRISMA_URL · POSTGRES_URL_NON_POOLINGShare-link database - both required-

Access and behaviour

VariableWhat it doesDefault
HTTP_BASIC_AUTH_USERNAME · HTTP_BASIC_AUTH_PASSWORDCredentials for the basic-auth middleware, which also has to be enabled in a build - below-
AIX_STRICT_PARSINGtrue makes the wire parser throw in production instead of warning. For diagnosing an AI service whose API driftedwarn only

Compiled into the browser bundle

These are read when the JavaScript is built, so they take effect only in a build you run.

VariableWhat it doesDefault
NEXT_PUBLIC_MOTDThe message-of-the-day banner - below-
NEXT_PUBLIC_GOOGLE_DRIVE_CLIENT_IDGoogle Drive in the attachment menu-
NEXT_PUBLIC_PLANTUML_SERVER_URLWhere diagrams are renderedhttps://www.plantuml.com/plantuml/svg/
NEXT_PUBLIC_GA4_MEASUREMENT_ID · NEXT_PUBLIC_POSTHOG_KEYYour own analytics properties - below-
NEXT_PUBLIC_BUILD_HASHOverrides the build identifier the app displaysgit revision, else 2-dev
NEXT_PUBLIC_DEBUG_BREAKStrue pauses the browser debugger on every error, critical and DEV log. Development builds only: npm run dev, never a published image-

Three rules apply to all of the above:

  • A host or endpoint value must carry its scheme. api.openai.com fails the build; https://api.openai.com passes.
  • A bad value stops the build, not a request - see When this doesn't work.
  • A key may be several keys, separated by commas, one picked at random per request. That spreads rate limits at no cost.
The field takesAI services
several keysAlibaba · Cerebras · Cohere · Deepseek · Gemini · Groq · Mistral · Moonshot · NVIDIA NIM · OpenRouter · Perplexity · Sakana · Together AI · xAI · Z.ai
one valueOpenAI · Anthropic · Azure · Bedrock · LocalAI · Ollama

Build time and run time

Everything named NEXT_PUBLIC_* is compiled into the JavaScript when the app is built. Everything else is read from the process environment while it runs.

What you doWhat happens
prebuilt image + -e OPENAI_API_KEY=...works immediately, no rebuild
prebuilt image + -e NEXT_PUBLIC_MOTD=...nothing happens, ever
a server-side variable changedtakes effect on restart
its name contains _API_every browser re-scans its model services once

The message of the day, Drive import, the analytics keys and the diagram server need a build you control: a Vercel project variable, or a build from source. The re-scan is Update.

Where to set them

You run it withPut them in
Source build.env in the project root
docker run--env-file .env, or one -e per variable
Composethe env_file entry the shipped file already has
KubernetesstringData in the secret manifest
Vercelthe project's Environment Variables screen

A variable that was accepted and changes nothing is one of the cases above, or a name that never reached the process. Everything else is My self-hosted instance is broken.

Check what the server read

/dev/debug on your instance lists what the server picked up as capability booleans - this provider configured, that one not - plus build information, and never a value. Download debug JSON captures the same for a report. A variable that reads false there never reached the process: the name, the file, or the restart is missing. A variable that reads true and still misbehaves is a request-time problem: My self-hosted instance is broken.

Keys and access control

A key in your server's environment belongs to the instance: everyone who opens it has working models and nobody types a secret. A key someone pastes into the app belongs to that browser alone. Both work at once, decided per service - and the person always wins: a key typed into the UI takes precedence over yours for that browser. Either way, whoever can reach the instance decides who spends, so access control goes in front of the app. The open branch ships no sign-in of its own and sits behind whatever authentication you already run.

What people see when the key is on the server

WhereWhat a person sees
first loadthe service creates itself, fetches its models, picks one per job
Models -> Add servicea lime mark on the provider's icon
the setup panelAPI key field: Already set on server, with a green check - the field stops being required

Nobody opens Models. The three browser-only vendors above - Cohere, LM Studio, Z.ai - still ask each person for a key, and Cohere's error names the difference: Missing Cohere API Key. Add it on the UI (Models Setup).

What a person can extract

Not the value. The browser is sent booleans - this provider is configured, that one is not - plus a hash and build information. No variable's value is ever serialised to the client. Provider-side, requests arrive from your server's address on your account: the dashboard attributes them to you, not to whoever asked.

The spend, entirely. In the open branch every API procedure is unauthenticated. Anyone who can reach the instance's API can bill your key without opening the app. Everything below is the fix.

What is reachable if you do nothing

Anyone who has the URL canWhat it costs you
Send model requests through the APIEvery key you set server-side, without limit
List which providers and services you configuredBooleans and your build hash, at /dev/debug
Run a web searchYour search quota
Synthesise speechYour OpenAI key, through the speech route
Fetch a transcriptYour server's bandwidth
Make your server fetch any URLYour remote-browser endpoint, unauthenticated by default
Write and read share linksRows in your database

The app's dev pages - /dev, /dev/debug, /dev/llms, /dev/inspect/* - are served in production: /dev/debug lists what the server picked up, and /dev/llms probes your configured models on a click.

Put authentication in front

The strongest options need no rebuild and cover every path, dev pages included. Each is its vendor's own procedure:

  • Cloudflare Access (Zero Trust) in front of any origin - their docs.
  • Vercel Deployment Protection - password or Vercel authentication on the project - their docs.
  • Your reverse proxy's own auth: auth_basic in nginx, basicauth in Caddy, forward-auth in Traefik.

Company SSO belongs in the same layer. An identity-aware proxy - oauth2-proxy, Authelia, Pomerium, Cloudflare Access - authenticates the person and passes an already-authorised request through. Big-AGI needs no integration for this, and has none.

Basic auth in the app

The repository ships middleware_BASIC_AUTH.ts. Rename it to middleware.ts and rebuild, then set HTTP_BASIC_AUTH_USERNAME and HTTP_BASIC_AUTH_PASSWORD. It is a build-time change, so the prebuilt image cannot have it.

Basic authPaths
covered/, everything under /api, a handful of app routes
not covered/workspace, /draw, /diff, /tokens, /dev/*

Your keys are covered; the dev pages are not. To cover everything with one control, use the platform or proxy option above.

Two response bodies tell you which mistake you made:

Response bodyWhat it means
Unauthorizedno credentials, or wrong ones
Unauthorized/Unconfiguredmiddleware enabled, the two variables are not set

The second also logs HTTP Basic Authentication is enabled but not configured on the server: the file was renamed and the variables were never set.

Never expose

  • The page-fetch route without authentication. It makes your server an open URL fetcher through your browser endpoint.
  • /dev/*, which lists your configuration.
  • Your remote-browser endpoint and your database. Private network only, never published ports.

Behind a reverse proxy

Streaming answers arrive as one long chunked application/json response on /api/* - not server-sent events. Four requirements follow:

  1. No response buffering and no caching on /api/*.
  2. Read and send timeouts well past a long generation - at least 300 seconds.
  3. A request body limit large enough for your attachments.
  4. Upgrade and Connection passed through, if you also want the in-app developer tooling.
location / {
  proxy_pass http://127.0.0.1:3000;
  proxy_http_version 1.1;
  proxy_set_header Host $host;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  proxy_buffering off;
  proxy_cache off;
  chunked_transfer_encoding on;
  proxy_read_timeout 300s;
  proxy_send_timeout 300s;
  client_max_body_size 100M;
}

Caddy and Traefik do not buffer by default; their timeout and body-size settings still need raising. Answers that appear all at once at the end, die partway, or bounce with **Request too large** are the requirements above being violated - the symptom-by-symptom decode is Stalled or cut off.

HTTPS

Caddy and Traefik obtain certificates by themselves; nginx and Apache use certbot - their documentation, not ours. The Big-AGI-specific reason it matters beyond secrecy: microphone capture, clipboard reads and screen capture are secure-context browser features. http://localhost counts as secure; a LAN address like http://192.168.1.10 does not. On a development machine, npm run dev-https serves the dev server over TLS.

Message of the day

NEXT_PUBLIC_MOTD renders a banner above the app for everyone on the instance. Build-time only, like every NEXT_PUBLIC_* variable, and each person can dismiss it permanently. Two placeholders expand at render: {{app_build_hash}} and {{app_build_pkgver}}.

What your instance reports

Nothing, by default. The two analytics integrations activate only when a build carries your own keys - NEXT_PUBLIC_GA4_MEASUREMENT_ID, NEXT_PUBLIC_POSTHOG_KEY - and then report to your properties, not ours. The shipped Dockerfile sets NEXT_TELEMETRY_DISABLED=1, so Next.js's own telemetry is off in every image built from it.

Fails without an error

What happensHow to tell
Someone types their own key into a service you configuredThey spend their own from that moment - there is no per-person quota and no way to force yours
Cerebras and Sakana answer requests and announce nothingNo lime mark, no green check, no self-configuration - add the service by hand, key field empty
The Already set on server label goes as soon as someone types a custom OpenAI hostYour server key is not used against an address the browser chose
Basic auth is on and /dev/* still answersLoad /dev/debug from outside your session; a stranger reads the same capability list
The microphone stops working on a LAN address over plain HTTPSecure-context features need HTTPS or localhost, and nothing in the app says so

When this doesn't work

A key that is set but never reaches the request says so per provider, in one shape: Missing OpenAI API Key. Add it on the UI or server side (your deployment). Confirm the server read it at all before touching anything else: Check what the server read. Errors the provider sent back are access errors from the provider. Streaming that stalls without a proxy in the path is Stalled or cut off. Anything else on your own deployment is My self-hosted instance is broken.

© 2026 Token Fabrics·Built with passion in San Diego