Skip to content

Blop Cloud environment variables

Server variables for a self-hosted Blop Cloud. Blop Cloud reads them from the process environment; the commented template is .env.example in the repository root. Never commit real values. CLI and collector variables are in Environment variables.

VariablePurpose
POSTGRES_URLPostgres connection string. Add ?sslmode=require for managed Postgres; use the pooler URL behind PgBouncer. The Compose file sets it.
AUTH_SECRETSigns the session cookie. Changing it signs everyone out.
BASE_URLExact public origin, with no path, query or fragment. An https value marks the session cookie Secure. Check runners must reach ${BASE_URL}/api/ingest.
VariableDefaultPurpose
PORT3000Listen port
HOST0.0.0.0Listen address
ORIGINDerived from the requestPublic origin trusted for same-origin checks; set it from BASE_URL behind a TLS proxy
PROTOCOL_HEADER, HOST_HEADERUnsetRead protocol and host from proxy headers such as x-forwarded-proto when ORIGIN is unset
ADDRESS_HEADERUnset: socket addressHeader that carries the client IP, used for rate limits and audit records
XFF_DEPTH1With ADDRESS_HEADER=x-forwarded-for, the number of trusted proxies; the client IP is that many entries from the end
BODY_SIZE_LIMIT512KRequest body cap; accepts K, M and G
POSTGRES_MAX_CONNECTIONS5Connection pool size
PROJECT_SECRETS_KEYAUTH_SECRETEncrypts saved project credentials. Rotating it makes them unreadable.
BLOP_PLUGINS_SAFE_MODEUnset1 starts only sign-in, permissions, health and machine endpoints (ingest, workers, webhooks) for recovery; no pages or user APIs
SENTRY_DSN, PUBLIC_SENTRY_DSNUnsetServer and browser error reporting; the browser DSN is public
AUTH_CODE_LIMIT_PER_EMAIL_IP5Sign-in codes per email and IP in 15 minutes
AUTH_CODE_LIMIT_PER_EMAIL30Sign-in codes per email in 15 minutes
AUTH_CODE_LIMIT_PER_IP20Sign-in codes per IP in 15 minutes
AUTH_CODE_LIMIT_PER_EMAIL_DAY50Sign-in codes per email per day
VariablePurpose
BLOP_MEMORY_DIRRequired for organization memory, no default or fallback: an absolute, writable, persistent directory holding one <workspace-id>.db per workspace. Unset returns memory_unconfigured. One app instance writes it.
BLOP_MEMORY_ASSESSOR_URLOptional operator-selected assessment endpoint. HTTPS or loopback HTTP; no credentials/query/fragment in URL. Configuration opts into processing selected authorized hosted memory excerpts and query data, not laptop files or raw telemetry. No redirects, retries or provider fallback.
BLOP_MEMORY_ASSESSOR_ID, BLOP_MEMORY_ASSESSOR_VERSION, BLOP_MEMORY_ASSESSOR_POLICY_VERSIONRequired nonempty version metadata when assessment is configured. Incomplete configuration fails closed, not not_configured.
BLOP_MEMORY_ASSESSOR_TOKENOptional server-only bearer credential for that endpoint; never included in receipts or browser bundles.
XDG_CACHE_HOMECache root for the pinned CPU embedding model (<root>/blop/models, else ~/.cache/blop/models). The model downloads only on explicit indexing.
BLOP_OAUTH_SIGNING_KEYSPrivate RSA JWK set ({"keys":[…]}) for RS256, each key with its own stable kid. Never expose it to clients.
BLOP_OAUTH_COOKIE_SECRETAt least 32 random characters, distinct from every other secret. Rotating it can make stored provider tokens unreadable.

Agent connections work only when BASE_URL and both OAuth variables are set. The OAuth issuer is ${BASE_URL}/oauth and the MCP resource is ${BASE_URL}/api/mcp.

No model is installed or selected implicitly. With no configuration, reads report assessment.status: not_configured. A configured endpoint is an internal assessor, not another agent tool. It must accept a bounded JSON POST:

{
"operation": "search",
"query": [{"text": "shipping"}],
"scopeHash": "<trusted-scope-hash>",
"graphRevision": 4,
"policyRevision": 1,
"coverageScope": "selected-memory",
"nodes": [{
"nodeId": "note:shipping",
"revision": 2,
"hash": "<sha256-of-supplied-source-body>",
"title": "Shipping",
"text": "Obtain owner review.",
"complete": true
}],
"missingInputs": []
}

Return this strict response, with actual findings when present:

{
"findings": [{
"id": "review",
"severity": "warning",
"summary": "Check owner review before shipping.",
"nodeIds": ["note:shipping"]
}],
"coverage": {"status": "complete", "missingInputs": []}
}

Severity is info|warning|high; coverage is complete|partial. At most four findings, four addressed node IDs/finding, 512 characters/summary and twenty 256-character missing-input descriptions. Unselected citation IDs and extra fields, including approval claims, fail validation. Text and judgments remain untrusted data; neither grants authorization.

Input covers at most sixteen selected nodes and 4,000 Unicode characters/body, within a 60,000-byte node budget and 100,000-byte request limit. Missing excerpts, task context, bounded pages and omitted brief/evidence text produce explicit partial coverage codes. Body hashes bind supplied source text plus exact node revisions; search text may already be a bounded excerpt. No evidence body is fetched by assessment. coverage.scope is selected-memory; workingTree is not_supplied, never an assessment of an unseen diff.

Default deadline is ten seconds; responses cap at 32,000 bytes. Receipts retain query/input hashes, selected source revisions/hashes, graph/policy versions, operator-supplied assessor/policy versions and assessment time. No assessment cache is used. Graph/grant changes during processing invalidate the result. Configured failure returns assessment_unavailable or assessment_failed with no unassessed result. Existing envelopes, identifiers and null/not-found semantics remain intact. Oversized final results require a smaller page/section, not identifier truncation.

The public graph API accepts MemoryDatabase.view(access, { assessor }) and exports assessMemoryRead, MemoryAssessor and createHttpMemoryAssessor for trusted in-process or explicitly selected local adapters. Assessed views log served reads only after assessment succeeds. Standalone local CLI reads remain accountless and unconfigured by default. Contract/loopback tests do not qualify model quality.

Any S3-compatible bucket, such as MinIO.

VariablePurpose
CLOUDFLARE_R2_ENDPOINTS3 endpoint URL, for example http://127.0.0.1:9000
CLOUDFLARE_R2_BUCKETBucket name
CLOUDFLARE_R2_ACCOUNT_IDAccount ID; any value for MinIO
CLOUDFLARE_R2_ACCESS_KEY_ID, CLOUDFLARE_R2_SECRET_ACCESS_KEYBucket credentials
ARTIFACT_RETENTION_DAYSDays before the cleanup worker deletes an artifact; default 30

Every worker accepts Authorization: Bearer $CRON_SECRET on GET or POST. A per-worker secret is an alternative for that one worker, sent in its own header, never as the bearer token.

VariableHeader
CRON_SECRETAuthorization: Bearer on every worker
HEALER_WORKER_SECRETx-healer-worker-secret
SLACK_DIGEST_WORKER_SECRETx-slack-digest-worker-secret
LINEAR_SYNC_WORKER_SECRETx-linear-sync-worker-secret
JIRA_SYNC_WORKER_SECRETx-jira-sync-worker-secret

The worker schedule is in Self-host. Also call /api/platform/health-tick every few minutes when workspaces use platform connections; it needs CRON_SECRET.

VariablePurpose
OPENROUTER_API_KEY, CHAT_AGENT_PROVIDER, CHAT_AGENT_MODEL, CHAT_AGENT_API_KEY, CHAT_MAX_CONCURRENT_RUNSIn-app chat. Without a key, chat shows a setup message.
CHAT_AGENT_MODELSModels people can pick in chat: a comma list of slug or slug=Label. The default model is always first, and any other model falls back to it. Unset offers a built-in list of free models.
BLOP_AGENT_PROVIDER, BLOP_AGENT_MODEL, BLOP_AGENT_API_KEYBrowser checks started from Blop Cloud. Each falls back to its CHAT_AGENT_* value, then to openrouter, anthropic/claude-sonnet-4, and OPENROUTER_API_KEY then OLLAMA_API_KEY.
OLLAMA_API_KEYOllama Cloud as an OpenAI-compatible provider
HEALER_PROVIDER, OPENAI_API_KEY, OPENAI_HEALER_MODEL, KHADIM_API_KEY, KHADIM_HEALER_PROVIDER, KHADIM_HEALER_MODEL, OPENROUTER_HEALER_API_KEY, OPENROUTER_HEALER_MODELFix suggestions; configured providers are tried in order, else a template is used

These fallbacks apply only to Blop Cloud, never to the local blop CLI.

VariableDefaultPurpose
BLOP_BROWSERcamoufoxBrowser for checks started from Blop Cloud: chromium, camoufox, firefox or webkit
BLOP_CONTAINERIZED1Runs that browser in a container through the Docker socket, so Docker is required; 0, false or no turns it off
BLOP_CAMOUFOX_NETWORKBLOP_PLAYWRIGHT_NETWORK, else noneDocker network for the Camoufox container on the Nixpacks path
IntegrationVariables
GitHubGITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET, GITHUB_APP_PRIVATE_KEY, GITHUB_APP_WEBHOOK_SECRET, GITHUB_APP_REDIRECT_URI, GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_CLIENT_SECRET; BLOP_GITHUB_API_URL (default api.github.com) sets a GitHub Enterprise API base for REST calls
SlackSLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_SIGNING_SECRET, SLACK_REDIRECT_URI
EmailRESEND_API_KEY (blank: emails go to the server log), EMAIL_FROM
BillingSTRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET. Billing is off without the key; BLOP_BILLING=off forces it off. Purchases stay closed until BLOP_BILLING_LIVE=1.
EgressINTEGRATION_EGRESS_ALLOWLIST restricts public hosts; INTEGRATION_EGRESS_ALLOW_PRIVATE admits exact private hostnames, over HTTP too

Integration egress refuses redirects and private, loopback and metadata addresses by default, with bounded time and size. Destination credentials are saved per project in the app, not in the environment. Register these callback URLs with the providers:

  • GitHub App callback: ${BASE_URL}/api/auth/github/callback
  • GitHub App webhook: ${BASE_URL}/api/integrations/github/webhooks
  • Slack OAuth: ${BASE_URL}/api/integrations/slack/oauth-callback

Workspaces can register an external platform service URL. Every such URL must be HTTPS and public, with no redirects and bounded time and size.

VariablePurpose
PLATFORM_URL, PLATFORM_SERVICE_TOKENDefault connection and its bearer token; URL default http://localhost:3002
PLATFORM_EGRESS_ALLOWLISTOnly these hosts or .domains are allowed
PLATFORM_EGRESS_ALLOW_PRIVATEHosts allowed to be private or plain HTTP, for local development

The Explore pages can read a blop collector on the server’s own machine; a deployed server cannot reach a developer’s localhost.

VariableDefaultPurpose
BLOP_COLLECTOR_URLhttp://127.0.0.1:27811Collector base URL; must be loopback
BLOP_COLLECTOR_WORKSPACESUnset: no workspaceComma-separated workspace IDs allowed to read it