Skip to content

Commit 337930c

Browse files
trodemasterclaude
andcommitted
docs: add Mode 3 — MCP containers on Mac host via Docker Compose
Documents how to route MCP servers running in Docker containers through botlockbox on the Mac host for credential injection without any changes to the MCP image. Covers proxy env vars, CA cert volume mount, and per-runtime trust vars (Python, Node.js, Go, curl). Adds a reusable contrib/docker-compose.example.yml template. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent def3a77 commit 337930c

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,80 @@ In Docker, this is a two-stage entrypoint: start the proxy as root, then `exec s
281281
282282
---
283283
284+
### Mode 3: MCP containers on a Mac host (Docker Compose)
285+
286+
Run MCP servers in Docker containers while botlockbox provides credential injection from the Mac host. The containers carry **zero credentials** — all `Authorization` headers are injected by botlockbox before requests reach external APIs.
287+
288+
This works with any MCP server that respects standard HTTP proxy environment variables. No changes to MCP server images are required.
289+
290+
```mermaid
291+
flowchart TD
292+
subgraph mac["Mac Host"]
293+
SE["Secure Enclave\n(age-plugin-se)"]
294+
BLB["botlockbox\nlisten: 0.0.0.0:8080\n(launchd agent)"]
295+
CAPEM["~/.botlockbox/ca.pem"]
296+
SE -->|"decrypts silently\nno Touch ID"| BLB
297+
BLB -->|"--ca-cert"| CAPEM
298+
end
299+
300+
subgraph docker["Docker Desktop (bridge network)"]
301+
MCP1["MCP server A\nPython\nREQUESTS_CA_BUNDLE=\n/etc/botlockbox/ca.pem"]
302+
MCP2["MCP server B\nNode.js\nNODE_EXTRA_CA_CERTS=\n/etc/botlockbox/ca.pem"]
303+
MCP1 & MCP2 -->|"volume mount :ro"| VOL["/etc/botlockbox/ca.pem"]
304+
end
305+
306+
CAPEM -->|"bind mount"| VOL
307+
308+
MCP1 -->|"HTTPS_PROXY=\nhost.docker.internal:8080"| BLB
309+
MCP2 -->|"HTTPS_PROXY=\nhost.docker.internal:8080"| BLB
310+
BLB -->|"Authorization injected\nfrom enclave"| API["External APIs\nOpenAI, GitHub…"]
311+
```
312+
313+
**Prerequisites:**
314+
315+
1. botlockbox running on the Mac host (Mode 1 — launchd + Secure Enclave).
316+
2. `listen: "0.0.0.0:8080"` in `botlockbox.yaml` — the default `127.0.0.1` is unreachable from Docker's bridge network.
317+
3. CA cert written to `~/.botlockbox/ca.pem` via `--ca-cert ~/.botlockbox/ca.pem` in the launchd plist.
318+
319+
**Docker Compose config** (full template in `contrib/docker-compose.example.yml`):
320+
321+
```yaml
322+
x-botlockbox-proxy: &botlockbox-proxy
323+
HTTP_PROXY: "http://host.docker.internal:8080"
324+
HTTPS_PROXY: "http://host.docker.internal:8080"
325+
http_proxy: "http://host.docker.internal:8080"
326+
https_proxy: "http://host.docker.internal:8080"
327+
NO_PROXY: "localhost,127.0.0.1,*.local"
328+
329+
x-botlockbox-ca: &botlockbox-ca
330+
REQUESTS_CA_BUNDLE: /etc/botlockbox/ca.pem # Python (requests, httpx, boto3, openai-sdk)
331+
NODE_EXTRA_CA_CERTS: /etc/botlockbox/ca.pem # Node.js
332+
SSL_CERT_FILE: /etc/botlockbox/ca.pem # Go stdlib, Ruby net/http
333+
CURL_CA_BUNDLE: /etc/botlockbox/ca.pem # curl / libcurl
334+
335+
services:
336+
mcp-server:
337+
image: your-mcp-server-image:latest
338+
environment:
339+
<<: [*botlockbox-proxy, *botlockbox-ca]
340+
# No API keys here — botlockbox injects them.
341+
volumes:
342+
- "${HOME}/.botlockbox/ca.pem:/etc/botlockbox/ca.pem:ro"
343+
extra_hosts:
344+
- "host.docker.internal:host-gateway" # needed on Linux Docker; harmless on Mac
345+
```
346+
347+
**How it works:**
348+
349+
1. Docker Desktop resolves `host.docker.internal` to the Mac host IP automatically.
350+
2. `HTTPS_PROXY` causes the container's HTTP library to tunnel all HTTPS through botlockbox.
351+
3. botlockbox MITMs the TLS session with its ephemeral CA, injects the credential from the Secure Enclave, and forwards the request.
352+
4. The CA cert is mounted read-only from the Mac host; each language runtime trusts it via its own env var — no code changes, no image rebuilds.
353+
354+
**The MCP server's perspective:** it makes a normal HTTPS call to `api.openai.com`. The response arrives and credentials were never in its environment.
355+
356+
---
357+
284358
## CLI reference
285359

286360
### `botlockbox seal`

contrib/docker-compose.example.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# contrib/docker-compose.example.yml
2+
#
3+
# Example Docker Compose setup for running MCP servers in containers on a Mac
4+
# host while botlockbox provides credential injection from the host.
5+
#
6+
# Prerequisites:
7+
# 1. botlockbox is running on the Mac host via launchd (Mode 1 — Secure Enclave).
8+
# 2. botlockbox.yaml uses listen: "0.0.0.0:8080" (NOT 127.0.0.1).
9+
# Without this, Docker containers cannot reach the proxy.
10+
# 3. The CA cert has been written to ~/.botlockbox/ca.pem via
11+
# --ca-cert ~/.botlockbox/ca.pem in the launchd plist.
12+
#
13+
# Usage:
14+
# docker compose -f contrib/docker-compose.example.yml up
15+
#
16+
# No credentials are placed in any container.
17+
# botlockbox on the Mac host injects Authorization / API headers
18+
# transparently before forwarding requests to external APIs.
19+
20+
# ──────────────────────────────────────────────────────────────────────────────
21+
# Reusable YAML anchors — merge these into any service's environment block.
22+
# ──────────────────────────────────────────────────────────────────────────────
23+
24+
x-botlockbox-proxy: &botlockbox-proxy
25+
# Route all HTTP/HTTPS traffic through botlockbox on the Mac host.
26+
# Docker Desktop resolves host.docker.internal to the host IP automatically.
27+
HTTP_PROXY: "http://host.docker.internal:8080"
28+
HTTPS_PROXY: "http://host.docker.internal:8080"
29+
http_proxy: "http://host.docker.internal:8080"
30+
https_proxy: "http://host.docker.internal:8080"
31+
# Keep container-local and LAN traffic off the proxy.
32+
NO_PROXY: "localhost,127.0.0.1,*.local"
33+
34+
x-botlockbox-ca: &botlockbox-ca
35+
# Trust the botlockbox ephemeral MITM CA for each language runtime.
36+
# All point at the same mounted PEM — no code changes or image rebuilds needed.
37+
REQUESTS_CA_BUNDLE: /etc/botlockbox/ca.pem # Python: requests, httpx, boto3, openai-sdk
38+
NODE_EXTRA_CA_CERTS: /etc/botlockbox/ca.pem # Node.js: all https / fetch
39+
SSL_CERT_FILE: /etc/botlockbox/ca.pem # Go stdlib, Ruby net/http
40+
CURL_CA_BUNDLE: /etc/botlockbox/ca.pem # curl, libcurl (many CLIs)
41+
42+
services:
43+
# ──────────────────────────────────────────────────────────────────────────
44+
# Replace this with your actual MCP server image.
45+
# The only required additions are the environment and volumes blocks below.
46+
# ──────────────────────────────────────────────────────────────────────────
47+
mcp-server:
48+
image: your-mcp-server-image:latest
49+
environment:
50+
<<: [*botlockbox-proxy, *botlockbox-ca]
51+
# Your MCP server's own non-secret config goes here.
52+
# API keys / tokens are NOT needed — botlockbox injects them.
53+
# MCP_SERVER_PORT: "3000"
54+
volumes:
55+
# Mount the botlockbox CA cert from the Mac host (read-only).
56+
- "${HOME}/.botlockbox/ca.pem:/etc/botlockbox/ca.pem:ro"
57+
extra_hosts:
58+
# Ensures host.docker.internal resolves on Linux Docker hosts too.
59+
# Ignored (harmless) on Docker Desktop for Mac.
60+
- "host.docker.internal:host-gateway"
61+
62+
# ──────────────────────────────────────────────────────────────────────────
63+
# Second service example: a Python-based MCP server.
64+
# Add as many services as needed; each gets the same proxy + CA config.
65+
# ──────────────────────────────────────────────────────────────────────────
66+
mcp-fetch:
67+
image: ghcr.io/modelcontextprotocol/mcp-server-fetch:latest
68+
environment:
69+
<<: [*botlockbox-proxy, *botlockbox-ca]
70+
volumes:
71+
- "${HOME}/.botlockbox/ca.pem:/etc/botlockbox/ca.pem:ro"
72+
extra_hosts:
73+
- "host.docker.internal:host-gateway"

0 commit comments

Comments
 (0)