Configuration reference
Every Bloom setting in .env.example, its default and what it changes.
Bloom reads its configuration from environment variables. Its .env.example carries the full key list and default values; an empty default means the value is not set. The install requires only a subset of these settings. Change the remaining defaults only to harden or tune a deployment.
Install
Section titled “Install”The keys every deployment sets. See Install.
| Setting | Default | What it does |
|---|---|---|
BLOOM_VERSION |
stable |
Image tag to run. In production, pin an immutable version or use stable, the newest stable release. See Image tags. |
BLOOM_ENV |
production |
Production mode. Startup rejects a missing or weak SECRET_KEY, requires a real ADMIN_EMAIL and an administrator password of at least 16 characters, logs JSON when LOG_JSON is unset, and sends auth cookies only over HTTPS by default. |
POSTGRES_USER |
bloom |
Database user. |
POSTGRES_PASSWORD |
empty | Database password. Required. |
POSTGRES_DB |
bloom |
Database name. |
SECRET_KEY |
empty | Signing key. Required. A different key invalidates sessions and pending invite and reset links. |
ADMIN_EMAIL |
empty | First administrator’s email. Production requires a real address. |
ADMIN_PASSWORD |
empty | First administrator’s password, at least 16 characters in production. |
ADMIN_FULL_NAME |
Bloom Admin |
First administrator’s name. |
AUTO_SEED_ADMIN |
false |
true creates the first administrator from the ADMIN_* values on a new database. With false, those values satisfy startup validation but create no account; create the administrator at /setup. Cloudron disables seeding. |
APP_BASE_URL |
http://localhost:8000 |
Public URL of this Bloom instance. Used in the links Bloom emails (invites, email verification, password resets) and in campaign links. |
BLOOM_APP_URL |
http://localhost:8000 |
Public Bloom URL. Set it in both the Bloom and the Bud .env. See Connecting to Bud. |
Operational
Section titled “Operational”# Leave the interactive API docs off in production: they expose the full# schema and every endpoint to anyone who can reach the host.ENABLE_DOCS=false
# One-off consistency pass at startup. Off by default; turn on only when a# release note asks for it.RUN_STARTUP_DATA_REPAIR=false
LOG_LEVEL=INFOLOG_JSON=trueENABLE_METRICS=true
# Browser origins allowed to call the API (JSON array). Set your public URL(s) in production.CORS_ORIGINS=["https://bloom.example.com"]
# Access and refresh token lifetimes; auth cookies are HTTPS-only in production.ACCESS_TOKEN_EXPIRE_MINUTES=30REFRESH_TOKEN_EXPIRE_DAYS=30AUTH_COOKIE_SECURE=true| Setting | Default | What it does |
|---|---|---|
ENABLE_DOCS |
false |
Interactive API docs. Enable them only in a trusted development environment. |
RUN_STARTUP_DATA_REPAIR |
false |
One-off consistency pass at startup. Turn on only when a release note asks for it. |
LOG_LEVEL |
INFO |
Root log level (DEBUG, INFO, WARNING, …). Logs go to stdout. |
LOG_JSON |
true |
One JSON object per log line. Unset: JSON when BLOOM_ENV=production, text otherwise. |
ENABLE_METRICS |
true |
Prometheus metrics at /api/metrics. The endpoint is unauthenticated: keep it reachable only from your monitoring network, or set false. |
CORS_ORIGINS |
["https://bloom.example.com"] |
Browser origins allowed to call the API, as a JSON array. Set your public URL in production. |
ACCESS_TOKEN_EXPIRE_MINUTES |
30 |
Access token lifetime. |
REFRESH_TOKEN_EXPIRE_DAYS |
30 |
Refresh token lifetime. |
AUTH_COOKIE_SECURE |
true |
Send auth cookies only over HTTPS. Defaults to true in production. |
BLOOM_DOTENV_DISABLED |
1 |
Bloom reads configuration from the environment, not a .env file, in the container. |
Every response carries an X-Request-ID header, and an incoming X-Request-ID is propagated. Pass it from your reverse proxy to trace a request across the proxy, Bud and your log aggregator. In nginx:
proxy_set_header X-Request-ID $request_id;The metrics endpoint serves http_requests_total{method,path,status}, the http_request_duration_seconds{method,path} latency histogram, and the standard Python process and GC metrics.
| Setting | Default |
|---|---|
SMTP_ENABLED |
false |
SMTP_HOST |
smtp.example.com |
SMTP_PORT |
587 |
SMTP_USERNAME |
replace-with-smtp-username |
SMTP_PASSWORD |
replace-with-smtp-password |
SMTP_FROM_EMAIL |
noreply@example.com |
SMTP_REPLY_TO |
support@example.com |
SMTP_STARTTLS |
true |
SMTP_SSL |
false |
SMTP_TIMEOUT_SECONDS |
30 |
INVITE_TOKEN_TTL_HOURS |
72 |
EMAIL_VERIFICATION_TOKEN_TTL_HOURS |
24 |
PASSWORD_RESET_TOKEN_TTL_HOURS |
2 |
SMTP is optional for a single administrator, but required for invitations, password resets, email verification, and approved email changes. See Email and invites.
Bud connection
Section titled “Bud connection”| Setting | Default | What it does |
|---|---|---|
SERVICE_TOKEN_PEPPER |
empty | Peppers the service tokens Bloom issues to Bud. Required only for the optional Bud result sync. Use an independent random value of at least 32 characters. A different value invalidates Bud result-sync credentials. |
BUD_APP_URL |
empty | Bud’s public URL. Bloom navigation and execution links open Bud. |
See Connecting to Bud.
Issue tracker sync
Section titled “Issue tracker sync”INTEGRATION_ENCRYPTION_KEY is separate from the SERVICE_TOKEN_PEPPER used for Bud sync: it encrypts GitHub, GitLab, and Jira tokens and webhook secrets at rest. Bloom starts without it: only tracker credential operations fail closed. Generate it with:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"| Setting | Default | What it does |
|---|---|---|
INTEGRATION_ENCRYPTION_KEY |
empty | Fernet key for tracker credentials and webhook secrets. Only needed when GitHub, GitLab, or Jira issue synchronization is configured. A different key makes stored tracker secrets unreadable until they are rotated. |
See Issue tracker sync.
Attachments
Section titled “Attachments”# Only needed when GitHub, GitLab, or Jira issue synchronization is configuredINTEGRATION_ENCRYPTION_KEY=
BLOOM_ATTACHMENT_DIR=/app/attachments # Cloudron sets this to /app/data/attachmentsBLOOM_MAX_ATTACHMENT_SIZE=26214400 # 25 MiBBLOOM_MAX_DOCUMENT_ATTACHMENT_BYTES=262144000 # 250 MiB per documentBLOOM_MIN_ATTACHMENT_FREE_BYTES=1073741824 # 1 GiBBLOOM_MAX_CONCURRENT_ATTACHMENT_UPLOADS_PER_USER=1BLOOM_ATTACHMENT_UPLOADS_PER_15_MINUTES=10 # per-user rate limit
# Bloom reads configuration from the environment, not a .env file, in containerBLOOM_DOTENV_DISABLED=1| Setting | Default | What it does |
|---|---|---|
BLOOM_ATTACHMENT_DIR |
/app/attachments |
Attachment directory. Mount durable storage here. Cloudron sets it to /app/data/attachments. |
BLOOM_MAX_ATTACHMENT_SIZE |
26214400 |
Largest single file, 25 MiB. |
BLOOM_MAX_DOCUMENT_ATTACHMENT_BYTES |
262144000 |
Total attachments per document, 250 MiB. |
BLOOM_MIN_ATTACHMENT_FREE_BYTES |
1073741824 |
Free space Bloom keeps on the attachment disk, 1 GiB. |
BLOOM_ATTACHMENT_UPLOADS_PER_15_MINUTES |
10 |
Upload starts per user in a rolling fifteen-minute window. |
BLOOM_MAX_CONCURRENT_ATTACHMENT_UPLOADS_PER_USER |
1 |
Active uploads per user. |
ALLOWED_ATTACHMENT_MIME_TYPES |
unset | Advanced. JSON array of accepted attachment MIME types, such as ["application/pdf","application/json"]. Unset uses the built-in allowlist. |
Bloom streams each upload through a temporary file and promotes only a complete upload. Automated Bud report publishing uses a scoped service credential and is separate from the per-user allowance, with the same file, document, MIME type, and disk capacity checks. These controls limit resource exhaustion; they are not a malware scanner.
S3-compatible storage
Section titled “S3-compatible storage”Bloom can keep attachments in an S3-compatible bucket (AWS S3, MinIO, Hetzner, Ceph), with the same local mirror, migration command, and readiness report as Bud. Local storage remains the default.
BLOOM_STORAGE_BACKEND=s3BLOOM_STORAGE_LOCAL_MIRROR=trueBLOOM_S3_BUCKET=BLOOM_S3_ENDPOINT_URL=BLOOM_S3_REGION=BLOOM_S3_PREFIX=bloomBLOOM_S3_ACCESS_KEY_ID=BLOOM_S3_SECRET_ACCESS_KEY=| Setting | Default | What it does |
|---|---|---|
BLOOM_STORAGE_BACKEND |
local |
s3 keeps attachments in a bucket. |
BLOOM_STORAGE_LOCAL_MIRROR |
true |
Also keep every file in BLOOM_ATTACHMENT_DIR. Reads come from the bucket and fall back to the mirror when the bucket does not answer; the log then names the key as a warning. |
BLOOM_S3_BUCKET |
empty | Bucket name. The bucket must exist. |
BLOOM_S3_ENDPOINT_URL |
empty | Endpoint for any store that is not AWS. |
BLOOM_S3_REGION |
empty | Region, where the store needs one. |
BLOOM_S3_PREFIX |
bloom |
Prefix that keeps several instances apart in one bucket. |
BLOOM_S3_ACCESS_KEY_ID |
empty | Access key. Leave both keys empty to use the ambient AWS credentials, such as an instance role. |
BLOOM_S3_SECRET_ACCESS_KEY |
empty | Secret key. |
Copy existing files with python -m app.storage migrate --to s3. Each file keeps its storage name under the prefix, sizes are checked after the copy, and files already in the bucket are skipped, so the command can be run again. The local files stay where they are.
/api/ready reports storage: local, s3, or s3-unreachable when the bucket did not answer. It stays 200 in that case, because the database decides readiness and reads can still come from the mirror. The free-space reserve applies to the local directory; quotas in the bucket are yours to manage. On Cloudron, set these variables with cloudron env set and keep the mirror: it lives under /app/data and is part of Cloudron’s backup.
ReqIF import limits
Section titled “ReqIF import limits”Bloom accepts .reqif XML and .reqifz ZIP archives. These limits bound upload size, archive expansion, hierarchy depth, object counts, and processing time. Raise them only when a legitimate file is rejected, and raise the specific one named in the error.
REQIF_MAX_REQUEST_BYTES=26214400 # 25 MiB: whole uploadREQIF_MAX_MEMBER_BYTES=26214400 # 25 MiB: any single file inside the zipREQIF_MAX_ARCHIVE_ENTRIES=100 # files per archiveREQIF_MAX_COMPRESSION_RATIO=20 # decompression-bomb guardREQIF_MAX_OBJECTS=999 # SPEC-OBJECTs per importREQIF_MAX_RELATIONS=10000 # SPEC-RELATIONs per importREQIF_MAX_HIERARCHY_DEPTH=100 # nesting depthREQIF_PROCESSING_TIMEOUT_SECONDS=60REQIF_IMPORTS_PER_15_MINUTES=5 # per-user rate limitREQIF_STREAM_CHUNK_BYTES=1048576 # 1 MiBAn archive holds exactly one .reqif member. Bloom validates the import while streaming it, parses it in a time-limited worker process, and runs one active import per project. These controls limit malformed or resource-intensive imports; they are not a malware scanner.



