Deploy

Commit production configuration, run BitRouter under a supervisor, and expose its streaming API through a deliberate network boundary.

3 min readEdit this page

A production deployment adds three things to the local quickstart: an explicit config file, a process supervisor, and a network boundary. Keep those decisions together so the process cannot start with the wrong policy or become reachable before authentication is ready.

Production configuration

Scaffold a file, then commit the non-secret parts with the rest of your infrastructure:

bro init -c /etc/bitrouter/bitrouter.yaml
bro config validate -c /etc/bitrouter/bitrouter.yaml

The generated file starts local-first:

server:
  listen: "127.0.0.1:4356"
  skip_auth: true

Always pass -c in a deployment. Without it, config lookup depends on the process working directory and can fall through to zero-config defaults. An explicit missing path fails instead of silently starting with the wrong policy.

Relative paths are resolved from the config file's directory because the daemon changes into that directory on startup. Prefer absolute paths for deployed state:

server:
  control_socket: "/run/bitrouter/bitrouter.sock"

database:
  url: "sqlite:///var/lib/bitrouter/bitrouter.db"

Keep credentials outside the committed file:

providers:
  openai:
    api_key: "${OPENAI_API_KEY}"

Supply those values through a process-manager environment file or a secrets mount. bro config validate can check config structure without production secrets; unresolved variables are warnings, so a deploy gate should also reject unexpected warnings.

Use the same released version for the binary, config schema, and CI validator. The meaning of individual config blocks lives in Config file.

Run as a service

Use bro serve under a supervisor. It stays in the foreground and writes logs to stdout. bro start detaches and is intended for workstations or hosts without a supervisor.

# /etc/systemd/system/bitrouter.service
[Unit]
Description=BitRouter
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/bro serve -c /etc/bitrouter/bitrouter.yaml
EnvironmentFile=/etc/bitrouter/bitrouter.env
Restart=on-failure
RestartSec=2
User=bitrouter
Group=bitrouter
RuntimeDirectory=bitrouter
StateDirectory=bitrouter

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now bitrouter
systemctl status bitrouter

Set the environment file to mode 0600. Use RUST_LOG, such as RUST_LOG=info,bitrouter=debug, to control logging; server.log_level does not control the shipped binary in this release.

The Unix control socket is how status, reload, restart, and stop reach the daemon. Control commands need the same -c path as the running process, and filesystem permissions on the socket are its administration boundary.

Network and TLS

Set the inbound bind explicitly:

ExposureSafe baseline
One host onlylisten: 127.0.0.1:4356, skip_auth: true
Shared or remoteReverse proxy with TLS, skip_auth: false, virtual keys

Never combine a non-loopback listener with skip_auth: true. Anyone who reaches that port can otherwise spend the configured provider credentials without caller attribution.

The preferred shared-host shape is to keep BitRouter on loopback and terminate TLS in a reverse proxy. Long model responses are streams, so disable buffering and allow long reads:

location / {
    proxy_pass http://127.0.0.1:4356;
    proxy_http_version 1.1;
    proxy_buffering off;
    proxy_cache off;
    proxy_set_header Connection "";
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
    client_max_body_size 16m;
}

A reverse proxy provides transport security, not caller authentication. Complete Secure before accepting remote inference traffic.

Upstream timeouts

Inbound proxy timeouts and BitRouter's outbound provider timeouts are independent. Tune the provider side under upstream.timeouts:

upstream:
  timeouts:
    connect_secs: 10
    read_secs: 120
    pool_idle_secs: 90
    tcp_keepalive_secs: 60
    # total_secs: unset

read_secs is an idle timeout between upstream bytes, including during a stream. Leave total_secs unset unless the entire model call must have a wall-clock deadline. A slow provider can override these values in its own config block.

Verify the deployment

bro config validate -c /etc/bitrouter/bitrouter.yaml
bro status -c /etc/bitrouter/bitrouter.yaml
curl -s http://127.0.0.1:4356/health

GET /health proves the HTTP server is alive, not that an upstream model is usable. bro status adds the running process, listen address, and routable-model count.

How is this guide?

On this page