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.
Required variables
Section titled “Required variables”| Variable | Purpose |
|---|---|
POSTGRES_URL | Postgres connection string. Add ?sslmode=require for managed Postgres; use the pooler URL behind PgBouncer. The Compose file sets it. |
AUTH_SECRET | Signs the session cookie. Changing it signs everyone out. |
BASE_URL | Exact public origin, with no path, query or fragment. An https value marks the session cookie Secure. Check runners must reach ${BASE_URL}/api/ingest. |
Server and proxy variables
Section titled “Server and proxy variables”| Variable | Default | Purpose |
|---|---|---|
PORT | 3000 | Listen port |
HOST | 0.0.0.0 | Listen address |
ORIGIN | Derived from the request | Public origin trusted for same-origin checks; set it from BASE_URL behind a TLS proxy |
PROTOCOL_HEADER, HOST_HEADER | Unset | Read protocol and host from proxy headers such as x-forwarded-proto when ORIGIN is unset |
ADDRESS_HEADER | Unset: socket address | Header that carries the client IP, used for rate limits and audit records |
XFF_DEPTH | 1 | With ADDRESS_HEADER=x-forwarded-for, the number of trusted proxies; the client IP is that many entries from the end |
BODY_SIZE_LIMIT | 512K | Request body cap; accepts K, M and G |
POSTGRES_MAX_CONNECTIONS | 5 | Connection pool size |
PROJECT_SECRETS_KEY | AUTH_SECRET | Encrypts saved project credentials. Rotating it makes them unreadable. |
BLOP_PLUGINS_SAFE_MODE | Unset | 1 starts only sign-in, permissions, health and machine endpoints (ingest, workers, webhooks) for recovery; no pages or user APIs |
SENTRY_DSN, PUBLIC_SENTRY_DSN | Unset | Server and browser error reporting; the browser DSN is public |
AUTH_CODE_LIMIT_PER_EMAIL_IP | 5 | Sign-in codes per email and IP in 15 minutes |
AUTH_CODE_LIMIT_PER_EMAIL | 30 | Sign-in codes per email in 15 minutes |
AUTH_CODE_LIMIT_PER_IP | 20 | Sign-in codes per IP in 15 minutes |
AUTH_CODE_LIMIT_PER_EMAIL_DAY | 50 | Sign-in codes per email per day |
Organization memory and OAuth
Section titled “Organization memory and OAuth”| Variable | Purpose |
|---|---|
BLOP_MEMORY_DIR | Required 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_URL | Optional 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_VERSION | Required nonempty version metadata when assessment is configured. Incomplete configuration fails closed, not not_configured. |
BLOP_MEMORY_ASSESSOR_TOKEN | Optional server-only bearer credential for that endpoint; never included in receipts or browser bundles. |
XDG_CACHE_HOME | Cache root for the pinned CPU embedding model (<root>/blop/models, else ~/.cache/blop/models). The model downloads only on explicit indexing. |
BLOP_OAUTH_SIGNING_KEYS | Private RSA JWK set ({"keys":[…]}) for RS256, each key with its own stable kid. Never expose it to clients. |
BLOP_OAUTH_COOKIE_SECRET | At 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.
Assessment endpoint contract
Section titled “Assessment endpoint contract”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.
Artifact storage variables
Section titled “Artifact storage variables”Any S3-compatible bucket, such as MinIO.
| Variable | Purpose |
|---|---|
CLOUDFLARE_R2_ENDPOINT | S3 endpoint URL, for example http://127.0.0.1:9000 |
CLOUDFLARE_R2_BUCKET | Bucket name |
CLOUDFLARE_R2_ACCOUNT_ID | Account ID; any value for MinIO |
CLOUDFLARE_R2_ACCESS_KEY_ID, CLOUDFLARE_R2_SECRET_ACCESS_KEY | Bucket credentials |
ARTIFACT_RETENTION_DAYS | Days before the cleanup worker deletes an artifact; default 30 |
Scheduled worker variables
Section titled “Scheduled worker variables”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.
| Variable | Header |
|---|---|
CRON_SECRET | Authorization: Bearer on every worker |
HEALER_WORKER_SECRET | x-healer-worker-secret |
SLACK_DIGEST_WORKER_SECRET | x-slack-digest-worker-secret |
LINEAR_SYNC_WORKER_SECRET | x-linear-sync-worker-secret |
JIRA_SYNC_WORKER_SECRET | x-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.
AI provider variables
Section titled “AI provider variables”| Variable | Purpose |
|---|---|
OPENROUTER_API_KEY, CHAT_AGENT_PROVIDER, CHAT_AGENT_MODEL, CHAT_AGENT_API_KEY, CHAT_MAX_CONCURRENT_RUNS | In-app chat. Without a key, chat shows a setup message. |
CHAT_AGENT_MODELS | Models 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_KEY | Browser 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_KEY | Ollama 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_MODEL | Fix 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.
Browser variables
Section titled “Browser variables”| Variable | Default | Purpose |
|---|---|---|
BLOP_BROWSER | camoufox | Browser for checks started from Blop Cloud: chromium, camoufox, firefox or webkit |
BLOP_CONTAINERIZED | 1 | Runs that browser in a container through the Docker socket, so Docker is required; 0, false or no turns it off |
BLOP_CAMOUFOX_NETWORK | BLOP_PLAYWRIGHT_NETWORK, else none | Docker network for the Camoufox container on the Nixpacks path |
Integration variables
Section titled “Integration variables”| Integration | Variables |
|---|---|
| GitHub | GITHUB_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 |
| Slack | SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_SIGNING_SECRET, SLACK_REDIRECT_URI |
RESEND_API_KEY (blank: emails go to the server log), EMAIL_FROM | |
| Billing | STRIPE_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. |
| Egress | INTEGRATION_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
Platform connection variables
Section titled “Platform connection variables”Workspaces can register an external platform service URL. Every such URL must be HTTPS and public, with no redirects and bounded time and size.
| Variable | Purpose |
|---|---|
PLATFORM_URL, PLATFORM_SERVICE_TOKEN | Default connection and its bearer token; URL default http://localhost:3002 |
PLATFORM_EGRESS_ALLOWLIST | Only these hosts or .domains are allowed |
PLATFORM_EGRESS_ALLOW_PRIVATE | Hosts allowed to be private or plain HTTP, for local development |
Same-machine collector variables
Section titled “Same-machine collector variables”The Explore pages can read a blop collector on the server’s own machine; a deployed server cannot reach a developer’s localhost.
| Variable | Default | Purpose |
|---|---|---|
BLOP_COLLECTOR_URL | http://127.0.0.1:27811 | Collector base URL; must be loopback |
BLOP_COLLECTOR_WORKSPACES | Unset: no workspace | Comma-separated workspace IDs allowed to read it |