← Docs

Self-host

Run the full engine on your own machine, a box on your tailnet, Docker, or Fly.io. Connect Claude Code, Claude Desktop, Cursor, claude.ai, or ChatGPT.

Self-hosting is the default and it is the whole engine. There is no feature that only appears when we run it for you. Pick the shape that fits.

You wantPath
Bourdon inside one machine's agentsLocal, stdio
An endpoint other machines on your LAN or tailnet can reachLocal, HTTP
Always-on, reachable from claude.ai or ChatGPTAlways-on and OAuth
Two boxes that answer each otherFederate two instances

Install#

pip install 'bourdon[server]'
# or, from source
git clone https://gitlab.com/bourdonai/bourdon && cd bourdon && pip install -e '.[server]'

1. Local, stdio#

The zero-config path. The MCP client launches the server over stdio and talks to it directly; no ports, no tokens.

Claude Code:

claude mcp add bourdon -- bourdon serve

Claude Desktop (claude_desktop_config.json):

{ "mcpServers": { "bourdon": { "command": "bourdon", "args": ["serve"] } } }

Other hosts are covered one file each under docs/integrations/.

2. Local, HTTP#

For an endpoint other machines can reach. Published images, no build required:

docker run -d -p 7500:7500 -v bourdon-data:/data --name bourdon bourdonai/bourdon
docker exec bourdon sh -c \
  'cat /data/.bourdon/bootstrap-token && rm /data/.bourdon/bootstrap-token'

The first boot mints an owner token into a mode-0600 bootstrap file. The second command reads and deletes it; token material never enters container logs, and the registry keeps only the hash. Point a client at it:

{
  "mcpServers": {
    "bourdon": {
      "url": "http://<this-host>:7500/mcp",
      "headers": { "Authorization": "Bearer bdn_…" }
    }
  }
}

Reaching it by a hostname? Declare it. Requests whose Host header the server does not recognise are refused with 421 before authentication, as a DNS-rebinding guard. A container binding 0.0.0.0 binds no particular name, so:

docker run -e BOURDON_ALLOWED_HOSTS="my-box.local" ...

Hostnames only, comma-separated, and *.example.org works for a subdomain. If you skip this, every request 421s and the endpoint looks dead rather than misconfigured; the server logs a warning naming this at startup.

The same image is published to two registries with the same digest:

RegistryReferenceBest for
Docker Hubbourdonai/bourdonpeople; it is what docker run finds
GitLabregistry.gitlab.com/bourdonai/bourdonautomation; no anonymous pull limit

Pin a digest for anything unattended:

docker pull bourdonai/bourdon:latest
docker image inspect --format '{{index .RepoDigests 0}}' bourdonai/bourdon:latest

docker compose up -d --build with the repository's docker-compose.yml does the same with a build from source. Manage tokens any time:

docker exec bourdon bourdon agent list
docker exec bourdon bourdon agent rotate owner                  # old token dies immediately
docker exec bourdon bourdon agent add teammate --tier trusted   # a second token

3. Always-on#

The repository ships a fly.toml for Fly.io; the same container runs anywhere that can hold a volume. The non-negotiables for any public bind:

  • authentication configured before the server will start on a non-loopback host;
  • --allowed-host (or BOURDON_ALLOWED_HOSTS) set to every public name;
  • TLS in front, because remote peers and browser clients require HTTPS;
  • --allowed-origin for the browser origins that will call it through a TLS-terminating proxy.

The full recipe, including the volume layout and the bootstrap-token step on a microVM, is docs/SELF_HOST.md.

4. Point a client at it#

Any MCP client whose configuration accepts headers connects with a token you minted; nothing to register. Cursor gets a one-click link:

bourdon agent add cursor --tier quarantined
bourdon cursor connect https://<your-host>

The link carries a reference to a BOURDON_TOKEN environment variable rather than the token itself, so it is safe to share.

5. Connect claude.ai or ChatGPT#

Browser clients cannot carry a header, so Bourdon has an OAuth front door with no identity provider and no account:

bourdon oauth set-passphrase          # gates the consent page
bourdon serve --transport http --oauth-public-url https://<your-host>

Presence of --oauth-public-url turns it on. The connector flow lands on a consent page you unlock with the passphrase, choosing which member identity the browser client acts as. bourdon oauth status reports whether the store, passphrase, and grants are in place. What the flow promises and what it does not is written down in docs/oauth-security-model.md.

6. Federate two of your own instances#

# on each side, mint a token for the other and list it as a peer
bourdon agent add <other> --tier trusted
bourdon serve --transport http --host 0.0.0.0 --peer https://<other>/ --federate

Depth is one, slow peers are dropped under a per-peer timeout, and peer rows are tagged with their origin. See How federation works.

Security notes#

  • Default bind is loopback. Non-loopback without configured auth exits non-zero at startup.
  • --allow-unauthenticated is honoured on loopback only.
  • Tokens are shown once, stored as SHA-256 hashes, compared in constant time, and never logged.
  • Every operation, allowed or denied, is appended to ~/.bourdon/audit.jsonl.

The whole model is on the Security page.