deploy/docker-compose.yml is stale template boilerplate — contradicts the real deployment and sets an env var the code never reads #5

Closed
opened 2026-09-10 19:12:17 -04:00 by claude · 1 comment
Contributor

The defect

deploy/docker-compose.yml is unedited docs-mcp-template boilerplate. It has never been
touched since the initial scaffold commit (9ba615c) — it still carries literal <product>,
<registry> and <owner> placeholders, and it describes a deployment that does not exist.

Worse, it sets an env var the code does not read:

# deploy/docker-compose.yml:33
MCP_ALLOWED_HOSTS: "<product>-docs-mcp,localhost,127.0.0.1"

grep -rn "os.environ\|getenv" docs_mcp/ rag/ — MCP_ALLOWED_HOSTS appears nowhere.
The knob that actually governs DNS-rebinding behaviour is MCP_DISABLE_DNS_REBINDING_PROTECTION
(docs_mcp/server.py:725). Anyone who trusted this file and set an allowlist would get a 421
on every request and no clue why.

What production actually runs

crop-chem-docs is not deployed as a standalone stack. It is the chem-mcp service inside
Drawbar's parent compose (Drawbar/drawbar-backend/docker-compose.yml):

  chem-mcp:
    image: git.jpaul.io/justin/crop-chem-docs:corpus-2026.05.24
    environment:
      MCP_TRANSPORT: streamable-http
      MCP_HOST: 0.0.0.0
      MCP_PORT: "8080"
      MCP_DISABLE_DNS_REBINDING_PROTECTION: "1"
      OLLAMA_URL: ${CHEM_OLLAMA_URL:-http://host.docker.internal:11434}
      EMBED_MODEL: ${CHEM_EMBED_MODEL:-nomic-embed-text}
    extra_hosts:
      - "host.docker.internal:host-gateway"
    expose:
      - "8080"
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: "true"

Every material line disagrees with what this repo ships:

deploy/docker-compose.yml says Production does
Shape standalone stack, own network + watchtower one service merged into Drawbar's compose
Port ports: "8000:8000" (published to host) expose: 8080, MCP_PORT=8080, no host port
Rebind protection MCP_ALLOWED_HOSTS (not read) MCP_DISABLE_DNS_REBINDING_PROTECTION=1
Ollama not set → image default http://ollama:11434 host.docker.internal:11434 + extra_hosts
Rerank http://<product>-rerank:8080 image default http://llama-rerank:8080
Service name <product>-docs-mcp chem-mcp

The OLLAMA_URL row is not cosmetic: Drawbar's ollama service is commented out in the
parent compose, so the image's baked default (http://ollama:11434, Dockerfile:49) does not
resolve. Query-time embedding — i.e. all of search_docs — depends on that override.

deploy/drawbar-compose-snippet.md is closer to reality but also drifted: it names the service
crop-chem-docs, maps ports: "8001:8000", and asserts "No environment block needed — the
image's defaults handle it"
, which is false on both the rebind and Ollama counts above.

The fix

Model it on justin/seed-mcp:deploy/docker-compose.yml, which the sibling repo already got
right: not a standalone stack, but the service block to merge into Drawbar's parent compose,
with a header comment saying exactly that and where the parent lives.

  1. Replace deploy/docker-compose.yml with the real chem-mcp block, copied from
    Drawbar/drawbar-backend/docker-compose.yml so the two cannot drift silently. Keep the
    inline comments explaining why rebind protection is off (internal-network-only, expose
    not ports) and why Ollama goes through host.docker.internal.
  2. Delete the MCP_ALLOWED_HOSTS line. It is not a supported knob in this codebase.
    (If a real allowlist is wanted instead of an on/off switch, that is a code change — see
    justin/zerto-docs#12, which made allowed_hosts/origins genuinely configurable. Out of
    scope here; file it separately if you want it.)
  3. Reconcile deploy/drawbar-compose-snippet.md with the same source of truth — service
    name chem-mcp, expose: 8080, and drop the "no environment block needed" claim. If the
    rewritten compose file makes the snippet redundant, delete the snippet and update the
    README tree instead of maintaining two half-true copies.
  4. Update the README file tree (README.md:93-96) to match whatever survives step 3.

Also note while you are in there: the snippet's verification block calls corpus_status(),
which does exist (server.py:536) — the five real tools are search_docs, get_page,
list_versions, corpus_status, crop_chem_api_lessons. That part is accurate; leave it.

Verification

This is documentation/config only — no code path changes, no eval run needed.

  • grep -rn "MCP_ALLOWED_HOSTS\|<product>\|<registry>\|<owner>" deploy/ README.md returns nothing.
  • Every env var named in deploy/ is one the code actually reads (cross-check against
    grep -rn "os.environ" docs_mcp/ rag/).
  • The service block in deploy/docker-compose.yml is byte-comparable with the chem-mcp
    block in Drawbar/drawbar-backend/docker-compose.yml modulo comments.

Related observation — not part of this issue

Production pins chem-mcp to image: ...crop-chem-docs:corpus-2026.05.24 while carrying
com.centurylinklabs.watchtower.enable: "true"
. Watchtower only re-pulls the tag a container
is running, and corpus-YYYY.MM.DD is a fresh unique tag per build (refresh.yml:153), so that
tag will never be re-pushed — the monthly corpus refresh has been shipping :latest to a
production container that cannot see it. seed-mcp runs :latest and does move. That is a
Drawbar-side deploy question, not a crop-chem-docs one; file against Drawbar/drawbar-backend
if it is not intentional.

Related: #4 (mcp 2.x migration — this mismatch was found while scoping it).

## The defect `deploy/docker-compose.yml` is **unedited docs-mcp-template boilerplate**. It has never been touched since the initial scaffold commit (9ba615c) — it still carries literal `<product>`, `<registry>` and `<owner>` placeholders, and it describes a deployment that does not exist. Worse, it sets an env var the code does not read: ```yaml # deploy/docker-compose.yml:33 MCP_ALLOWED_HOSTS: "<product>-docs-mcp,localhost,127.0.0.1" ``` `grep -rn "os.environ\|getenv" docs_mcp/ rag/` — `MCP_ALLOWED_HOSTS` appears **nowhere**. The knob that actually governs DNS-rebinding behaviour is `MCP_DISABLE_DNS_REBINDING_PROTECTION` (`docs_mcp/server.py:725`). Anyone who trusted this file and set an allowlist would get a 421 on every request and no clue why. ## What production actually runs crop-chem-docs is **not** deployed as a standalone stack. It is the `chem-mcp` service inside Drawbar's parent compose (`Drawbar/drawbar-backend/docker-compose.yml`): ```yaml chem-mcp: image: git.jpaul.io/justin/crop-chem-docs:corpus-2026.05.24 environment: MCP_TRANSPORT: streamable-http MCP_HOST: 0.0.0.0 MCP_PORT: "8080" MCP_DISABLE_DNS_REBINDING_PROTECTION: "1" OLLAMA_URL: ${CHEM_OLLAMA_URL:-http://host.docker.internal:11434} EMBED_MODEL: ${CHEM_EMBED_MODEL:-nomic-embed-text} extra_hosts: - "host.docker.internal:host-gateway" expose: - "8080" restart: unless-stopped labels: com.centurylinklabs.watchtower.enable: "true" ``` Every material line disagrees with what this repo ships: | | `deploy/docker-compose.yml` says | Production does | |---|---|---| | Shape | standalone stack, own network + watchtower | one service merged into Drawbar's compose | | Port | `ports: "8000:8000"` (published to host) | `expose: 8080`, `MCP_PORT=8080`, no host port | | Rebind protection | `MCP_ALLOWED_HOSTS` (**not read**) | `MCP_DISABLE_DNS_REBINDING_PROTECTION=1` | | Ollama | not set → image default `http://ollama:11434` | `host.docker.internal:11434` + `extra_hosts` | | Rerank | `http://<product>-rerank:8080` | image default `http://llama-rerank:8080` | | Service name | `<product>-docs-mcp` | `chem-mcp` | The `OLLAMA_URL` row is not cosmetic: Drawbar's `ollama` service is **commented out** in the parent compose, so the image's baked default (`http://ollama:11434`, Dockerfile:49) does not resolve. Query-time embedding — i.e. all of `search_docs` — depends on that override. `deploy/drawbar-compose-snippet.md` is closer to reality but also drifted: it names the service `crop-chem-docs`, maps `ports: "8001:8000"`, and asserts *"No environment block needed — the image's defaults handle it"*, which is false on both the rebind and Ollama counts above. ## The fix Model it on **`justin/seed-mcp:deploy/docker-compose.yml`**, which the sibling repo already got right: not a standalone stack, but the service block to *merge* into Drawbar's parent compose, with a header comment saying exactly that and where the parent lives. 1. **Replace `deploy/docker-compose.yml`** with the real `chem-mcp` block, copied from `Drawbar/drawbar-backend/docker-compose.yml` so the two cannot drift silently. Keep the inline comments explaining *why* rebind protection is off (internal-network-only, `expose` not `ports`) and why Ollama goes through `host.docker.internal`. 2. **Delete the `MCP_ALLOWED_HOSTS` line.** It is not a supported knob in this codebase. (If a real allowlist is wanted instead of an on/off switch, that is a code change — see justin/zerto-docs#12, which made allowed_hosts/origins genuinely configurable. Out of scope here; file it separately if you want it.) 3. **Reconcile `deploy/drawbar-compose-snippet.md`** with the same source of truth — service name `chem-mcp`, `expose: 8080`, and drop the "no environment block needed" claim. If the rewritten compose file makes the snippet redundant, delete the snippet and update the README tree instead of maintaining two half-true copies. 4. **Update the README file tree** (`README.md:93-96`) to match whatever survives step 3. Also note while you are in there: the snippet's verification block calls `corpus_status()`, which does exist (`server.py:536`) — the five real tools are `search_docs`, `get_page`, `list_versions`, `corpus_status`, `crop_chem_api_lessons`. That part is accurate; leave it. ## Verification This is documentation/config only — no code path changes, no eval run needed. - `grep -rn "MCP_ALLOWED_HOSTS\|<product>\|<registry>\|<owner>" deploy/ README.md` returns nothing. - Every env var named in `deploy/` is one the code actually reads (cross-check against `grep -rn "os.environ" docs_mcp/ rag/`). - The service block in `deploy/docker-compose.yml` is byte-comparable with the `chem-mcp` block in `Drawbar/drawbar-backend/docker-compose.yml` modulo comments. ## Related observation — not part of this issue Production pins `chem-mcp` to `image: ...crop-chem-docs:corpus-2026.05.24` **while carrying `com.centurylinklabs.watchtower.enable: "true"`**. Watchtower only re-pulls the tag a container is running, and `corpus-YYYY.MM.DD` is a fresh unique tag per build (`refresh.yml:153`), so that tag will never be re-pushed — the monthly corpus refresh has been shipping `:latest` to a production container that cannot see it. seed-mcp runs `:latest` and does move. That is a Drawbar-side deploy question, not a crop-chem-docs one; file against `Drawbar/drawbar-backend` if it is not intentional. Related: #4 (mcp 2.x migration — this mismatch was found while scoping it).
claude added the ai-readybugP2 labels 2026-09-10 19:12:28 -04:00
Author
Contributor

The deploy-pinning observation at the bottom of this issue is now filed as Drawbar/drawbar-backend#339 — confirmed against the registry: corpus-2026.08.11 and corpus-2026.09.01 both exist, so production has silently missed two refreshes. Stays out of scope here.

The deploy-pinning observation at the bottom of this issue is now filed as Drawbar/drawbar-backend#339 — confirmed against the registry: `corpus-2026.08.11` and `corpus-2026.09.01` both exist, so production has silently missed two refreshes. Stays out of scope here.
Sign in to join this conversation.