Self-hosted Spotlight settings

A self-hosted Spotlight is configured through environment variables, read once when it starts. Hosts have settings of their own; see Host settings.

Validation

Spotlight checks the variables when it starts. With any problem, it stops and lists each one:

Invalid environment variables:
  <variable>: <problem>

Neither SPOTLIGHT_SECRETS_KEY nor SECRETS_KMS_KEY_ID set gives SPOTLIGHT_SECRETS_KEY: wraps each workspace data key when SECRETS_KMS_KEY_ID names no AWS KMS key; generate one with: openssl rand -base64 32.

Service

VariableDefaultDescription
DATABASE_URLrequiredA postgres:// or postgresql:// URL.
PORT3000The port Spotlight listens on.
NODE_ENVdevelopmentdevelopment, test or production.
LOG_LEVELdebug in development, else infofatal, error, warn, info, debug, trace or silent.
SPOTLIGHT_RELEASEnoneThe release the install script installs, and the runner image tag it pulls. Without it, latest.
SPOTLIGHT_INSTALLER_DIR/opt/spotlight/installerThe directory holding the install.sh served at /api/install.sh.

Addresses and sign-in

VariableDefaultDescription
BETTER_AUTH_URLrequiredThe app's public URL, used for sign-in.
BETTER_AUTH_SECRETrequiredSigns sessions. At least 32 characters.
SPOTLIGHT_PUBLIC_URLBETTER_AUTH_URLThe Spotlight address in what Spotlight hands out: the install script's SPOTLIGHT_URL, invite links, links in Slack and on GitHub, the OAuth callback and the issuer of hook tokens.
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETrequiredThe GitHub OAuth app people sign in with.
GITHUB_APP_IDrequiredThe GitHub App that clones and pushes.
GITHUB_APP_PRIVATE_KEYrequiredThe GitHub App's private key, in PEM. \n in the value is read as a line break.
GITHUB_WEBHOOK_SECRETemptyThe GitHub App's webhook secret. While empty, every GitHub webhook is refused.

Secrets

VariableDefaultDescription
SPOTLIGHT_SECRETS_KEYnone32 bytes, base64 encoded, as made by openssl rand -base64 32. Wraps each workspace's data key, which seals secrets and the other credentials Spotlight keeps, such as integration connection tokens. Required unless SECRETS_KMS_KEY_ID is set.
SECRETS_KMS_KEY_IDnoneAn AWS KMS key that wraps workspace data keys. When set, used in place of SPOTLIGHT_SECRETS_KEY.
SECRETS_AWS_SECRETS_MANAGERfalsetrue lets Spotlight read a secret whose source is AWS Secrets Manager when it is used.

AWS KMS and AWS Secrets Manager are reached with the default AWS credential chain and region.

Never lose SPOTLIGHT_SECRETS_KEY. Without it, no sealed value can be opened: every secret has to be set again and every integration connected again.

Calls out of Spotlight

VariableDefaultDescription
OUTBOUND_ALLOWED_HOSTSemptyComma-separated hosts Spotlight may call for integrations and their tools, OAuth token exchanges, hooks it sends itself, and push pools' provision calls. An entry admits the host and its subdomains, compared without regard to case. Empty allows none.
OAUTH_CLIENTS{}The deployment's OAuth clients, as JSON mapping an integration's name to { "id", "secret" }, with issuer, the authorization server, for an MCP server it discovers. Example: {"slack":{"id":"...","secret":"..."}}.
HOOK_SIGNING_KEYnoneA P-256 private key, in PEM, that signs hook OIDC tokens with ES256. \n in the value is read as a line break. Without it, a hook with oidc is refused.
MCP_RELIST_SECONDS600How often the tools of an integration's MCP server are listed again.
SPOTLIGHT_CATALOG_URLhttps://catalog.usespotlight.aiWhere catalog tools are called. Always reached, whatever OUTBOUND_ALLOWED_HOSTS says.
SPOTLIGHT_CATALOG_TOKENnoneThe token sent with each catalog call.

Register each OAuth client with the callback <SPOTLIGHT_PUBLIC_URL>/api/integrations/oauth/callback. See OAuth connect and callback.

OAUTH_CLIENTS that is not JSON, or not of this shape, stops Spotlight at start with OAUTH_CLIENTS is not JSON: <error> or OAUTH_CLIENTS must map a source to { id, secret }, ....

Drafting

Change stacks, decisions, memories and judges use models through OpenRouter.

VariableDefaultDescription
OPENROUTER_API_KEYnoneWithout it, no decision or memory is drafted, no run is judged, and a change stack is refused with Change stacks are not set up on this deployment..
CHANGE_STACK_MODELgoogle/gemini-3.8-flashDrafts change stacks.
DECISIONS_MODELgoogle/gemini-3.8-flashDrafts decisions.
MEMORIES_MODELgoogle/gemini-3.8-flashDrafts memories.
JUDGE_MODELgoogle/gemini-3.8-flashThe judge.

Error reporting

VariableDefaultDescription
SENTRY_DSNnoneWhere Spotlight reports its errors. Without it, nothing is reported.
SENTRY_WEB_DSNSENTRY_DSNWhere the web app reports its errors.
SENTRY_ENVIRONMENTthe Sentry SDK's defaultThe environment on each report.

See Error reporting for what a report holds.

Storage

Attachments and workspace archives are kept in one S3 bucket. Browsers and runners never hold its keys: they upload and download through presigned URLs that last 15 minutes.

VariableDefaultDescription
S3_BUCKETrequiredThe bucket's name.
S3_REGIONus-east-1The bucket's region.
S3_ENDPOINTAWSThe S3 endpoint Spotlight itself calls. Set for storage other than AWS S3.
S3_FORCE_PATH_STYLEfalsetrue addresses the bucket by path, for S3_ENDPOINT and S3_RUNNER_ENDPOINT.
S3_PUBLIC_ENDPOINTS3_ENDPOINTThe endpoint in URLs presigned for browsers, such as a CDN.
S3_PUBLIC_FORCE_PATH_STYLEfalsetrue addresses the bucket by path in URLs presigned for browsers.
S3_RUNNER_ENDPOINTS3_ENDPOINTThe endpoint in URLs presigned for runners, for when S3_ENDPOINT is private.
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYnoneThe bucket user's keys. Without both, the default AWS credential chain.

Egress always admits the storage host runners reach: the host of S3_RUNNER_ENDPOINT, else of S3_ENDPOINT, else <bucket>.s3.<region>.amazonaws.com, or s3.<region>.amazonaws.com with S3_FORCE_PATH_STYLE.

Objects

KeyHolds
pending/<workspace id>/<attachment id>A file a browser has uploaded but no task, answer or message has taken yet.
workspaces/<workspace id>/attachments/<attachment id>A file a task, answer or message has taken.
workspaces/<workspace id>/archives/<archive id>.tar.gzA workspace archive.

A browser uploads to pending/. Once a task, answer or message that names the file is saved, Spotlight copies it under workspaces/ and the pending copy is dropped. Spotlight never removes an attachment under workspaces/. It removes archives itself, on the schedule in Limits and timeouts.

Bucket setup

Spotlight does not create the bucket. It needs:

  • Public access blocked.

  • A CORS rule for the app's origin:

    [
      {
        "AllowedOrigins": ["https://spotlight.acme.example"],
        "AllowedMethods": ["PUT", "GET"],
        "AllowedHeaders": ["content-type", "x-amz-meta-uploader", "x-amz-meta-name"],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
      }
    ]
  • A lifecycle rule that expires objects under pending/ after one day.

Never expire objects under workspaces/. An archive that expires takes its task's conversation with it.

The keys need only s3:PutObject, s3:GetObject and s3:DeleteObject, on arn:aws:s3:::<bucket>/pending/* and arn:aws:s3:::<bucket>/workspaces/*.

Mail

Spotlight sends invites by email.

VariableDefaultDescription
SMTP_URLnoneThe mail server, as in smtps://user:pass@smtp.example.com:465.
MAIL_FROMSpotlight <noreply@usespotlight.ai>The sender. Must be one the mail server permits.

With SMTP_URL unset, Spotlight sends nothing. It logs each message at warn with No mail server configured, so this message was only written down.

On this page