Configuration¶
BlackBull reads its runtime configuration from three sources, which compose with a deterministic precedence:
CLI flags > environment variables > TOML config file > defaults
A more specific source overrides a less specific one. This means you can ship a config file with sensible defaults, override individual values via env in your container, and override those again via CLI flags for one-off debugging.
Environment variables¶
Every runtime knob has a BB_* (or BLACKBULL_*) environment
variable. Setting it on the process environment is the most
common form of configuration:
BB_WORKERS=4 BB_UVLOOP=1 BB_ACCESS_LOG=1 \
blackbull myapp:app --bind 0.0.0.0:8080
The full table is in Reference — Environment variables. Highlights:
BLACKBULL_ENV—development(default) /production/test. Controls the error handler posture and whether static files are served.BB_WORKERS— pre-fork worker count (0resolves toos.cpu_count()).BB_ACCESS_LOG— toggle the access log (1on,0off).BB_UVLOOP— installuvloop's asyncio policy.
TOML config file¶
For larger deployments, write a TOML file and point the CLI at it:
blackbull myapp:app --config /etc/blackbull/config.toml
[server]
workers = 4
uvloop = true
keep_alive_timeout = 5
h2_max_concurrent_streams = 200
h2_enable_websocket = false
[limits]
max_connections = 1000
request_timeout = 30
header_timeout = 10
compression_min_size = 100
[logging]
access_log = true
async_logging = true
log_format = "json" # "" (plain, default) or "json"
syslog_addr = "127.0.0.1:514" # ship logs via UDP syslog (optional)
batch_size = 64 # async logging always batches; this is the coalescing width
batch_timeout_ms = 5 # max ms a partial batch waits before flush
file = "/var/log/blackbull/access.log" # write to a file instead of stderr (optional)
[tls]
cert = "/etc/blackbull/cert.pem"
key = "/etc/blackbull/key.pem"
Section + key names mirror the BB_* environment-variable
naming (lower-cased, the BB_ prefix dropped). Unknown
sections and keys are silently ignored, so future TOML keys
don't break older binaries.
The TLS keys ([tls] cert, [tls] key) are CLI-arg fallbacks
applied to --certfile / --keyfile when those aren't
specified on the command line.
CLI flags¶
The blackbull console script (and app.run(...) in code)
both expose the most-touched knobs as direct flags:
blackbull myapp:app \
--bind 0.0.0.0:8443 \
--certfile cert.pem --keyfile key.pem \
--workers 4 \
--max-connections 1000 \
--stream-queue-depth 128
See blackbull --help for the complete list. Anything not
exposed as a flag can still be set via the corresponding
BB_* environment variable.
Precedence in detail¶
For each knob, the resolved value is the first source that specifies it:
- CLI flag —
--workers 4wins over both env and TOML. - Environment variable —
BB_WORKERS=4wins over TOML. - TOML config file —
[server] workers = 4wins over the default. - Built-in default — listed in Reference — Environment variables.
The implementation hook is os.environ.setdefault(...) — TOML
values are only written into the environment when the variable
isn't already present, so existing env vars always win. CLI
flags then trump both.
In-code configuration¶
When embedding BlackBull (not running via the CLI), app.run()
accepts the same parameters as keyword arguments:
app.run(
port=8443,
certfile='cert.pem', keyfile='key.pem',
workers=4,
max_connections=1000,
stream_queue_depth=128,
)
Keyword arguments take precedence over environment variables in the same way the CLI flags do.
Declarative startup with AppConfig¶
To declare the startup settings once — rather than threading them
through every run() call site — build the app with an
AppConfig:
from blackbull import BlackBull, AppConfig
app = BlackBull(config=AppConfig(
port=8443,
certfile='cert.pem', keyfile='key.pem',
workers=4,
))
if __name__ == '__main__':
app.run() # picks up the bound config
AppConfig is a frozen dataclass holding exactly the parameters
run() accepts (port, certfile, keyfile, unix_path,
inherited_fd, workers, max_connections, stream_queue_depth,
ws_queue_depth, reload, reload_paths). It is not a
general-purpose settings store — server-tuning knobs stay in the
BB_* environment variables.
Resolution order in run() is, highest to lowest:
- an explicit
run(...)keyword argument —app.run(port=9000)always wins; - a
BLACKBULL_*environment variable, for the deploy-time settings (BLACKBULL_PORT,BLACKBULL_CERT,BLACKBULL_KEY,BLACKBULL_UNIX_PATH,BLACKBULL_RELOAD); - the same
BLACKBULL_*key in a.envfile in the working directory (needs the[dotenv]extra — see below); - the value declared on the bound
AppConfig; serve()'s built-in default.
app = BlackBull(config=AppConfig(port=8443, certfile='c.pem', keyfile='k.pem'))
app.run() # binds 8443 with TLS from the config
app.run(port=9000) # explicit arg overrides the config's 8443
# Same app.py, no code change — the env var overrides the config's 8443:
BLACKBULL_PORT=9000 BLACKBULL_CERT=/etc/ssl/c.pem BLACKBULL_KEY=/etc/ssl/k.pem \
python app.py
BLACKBULL_* is the deployment namespace (the handful of
settings you change per environment); server-tuning knobs
(workers, max_connections, queue depths, timeouts) keep their
BB_* variables — they are not duplicated under BLACKBULL_*. The
provenance of each non-default deploy setting is logged once at
startup on the blackbull.config logger, e.g.
config: port=9000 (from $BLACKBULL_PORT).
There is no host field / BLACKBULL_HOST: BlackBull's socket
layer binds dual-stack on all interfaces, so a per-interface host
would silently do nothing. Use unix_path or inherited_fd for
non-TCP binds.
.env files¶
Install the optional extra to let app.run() (and the blackbull
CLI) read BLACKBULL_* values from a .env file in the working
directory:
pip install 'blackbull[dotenv]'
# .env
BLACKBULL_PORT=8443
BLACKBULL_CERT=/etc/ssl/cert.pem
BLACKBULL_KEY=/etc/ssl/key.pem
The real process environment always wins over .env (so a
docker run -e BLACKBULL_PORT=9000 overrides the file). Without the
extra, .env files are ignored and only the real environment is
consulted — BLACKBULL_* resolution still works.
Serving static files with blackbull serve¶
For a quick static file server — a drop-in upgrade over
python -m http.server — point the serve subcommand at a
directory:
blackbull serve # serve ./ on http://127.0.0.1:8000
blackbull serve ./public --bind :8080
blackbull serve ./public --certfile cert.pem --keyfile key.pem # HTTPS + HTTP/2
Unlike python -m http.server, it ships:
- ETag / conditional requests — repeat fetches get a
304 Not Modified(disable with--no-etag); - HTTP/2 automatically once
--certfile/--keyfilemake it HTTPS; - a directory index (
index.htmlby default; change with--index NAME, disable with--index ''); - precompressed-sibling negotiation (
.br/.zst/.gz).
No application code is required. --cache holds file bodies in an
in-memory LRU for higher throughput at the cost of picking up
on-disk edits only after the per-entry stat TTL.
Note
blackbull serve is a development / standalone convenience.
StaticFiles does not serve files when BLACKBULL_ENV=production
(the expectation is a reverse proxy or CDN fronts static traffic
there) — see Static files.
Operational defaults to think about¶
The default values are tuned for development. For production, the knobs most worth a second look:
| Variable | Dev default | Production guidance |
|---|---|---|
BLACKBULL_ENV |
development |
Set to production. Tightens error responses and stops StaticFiles from competing with your reverse proxy. |
BB_WORKERS |
1 |
Set to 0 (= cpu_count()) or a fixed integer matching your CPU budget. |
BB_REQUEST_TIMEOUT |
0 (off) |
Set to a positive value (e.g. 30) so stalled handlers get evicted. |
BB_MAX_CONNECTIONS |
500 |
Tune for the worker's memory budget; set 0 only if some upstream caps connection counts. |
BB_UVLOOP |
0 |
Set to 1 for a typical 1.5-2× throughput improvement on HTTP/2 hot paths. |
Next¶
- Reference — Environment variables
— exhaustive table of every
BB_*knob. - Logging —
BB_ACCESS_LOGandBB_ASYNC_LOGGINGin action. - Error handling —
BLACKBULL_ENV's effect on the default error response.