Skip to content

refactor(compose)!: make seo-tools a standalone application - #8

Merged
siva01c merged 2 commits into
fix/sandbox-standalone-findingsfrom
fix/mcp-standalone
Sep 1, 2026
Merged

siva01c merged 2 commits into
fix/sandbox-standalone-findingsfrom
fix/mcp-standalone

Conversation

@siva01c

@siva01c siva01c commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Stacked on #7 — merge that first, and GitHub will retarget this to main.

seo-tools is a standalone application and this is a public repository, so docker-compose.yml should describe the app and nothing about where it runs. It didn't: the mcp service carried one particular deployment's shared token file, reverse-proxy vhost labels, external network and a bind mount into a sibling website checkout. A clean clone could not start it at all.

Note on the history. The first commit here took the opposite approach — it kept the deployment as the base and added docker-compose.standalone.yml as an escape hatch, with the frontend mount defaulting to ../ludekkvapil/public/seo. That was reviewed and rejected. The second commit inverts it; the overlay file is deleted. Read 5be77d0 for the shape that lands.

Why base-as-app is the only layering that works

Compose merges overlays additively. A network or volume in the base file can never be subtracted by an overlay — only added to. So "deployment as base, standalone as overlay" needs ${VAR:-…} parameterisation to fake subtraction, while "app as base, deployment as overlay" composes naturally. The rejected approach was fighting the tool.

What the base file loses

removed why
env_file: ../.env.shared a file that only exists in one deployment tree
VIRTUAL_HOST, LETSENCRYPT_HOST, VIRTUAL_PORT, VIRTUAL_PROTO, VIRTUAL_NETWORK, HTTPS_METHOD reverse-proxy discovery labels, inert without that proxy
networks: [agentic-ops] + the top-level networks: block an external network the clone cannot create
../ludekkvapil/public/seo mount a sibling repo; now ${SEO_FRONTEND_DIR:-./storage/frontend}
docker-compose.standalone.yml with the base standalone, nothing left to override

Comments that narrated one particular VPS were rewritten to state the constraint rather than the host — "only a trusted reverse proxy may set X-Forwarded-For" survives, the topology doesn't. Same for mcp-server.ts and docs/security.md.

NODE_ENV=production and the mandatory SEO_MCP_TOKEN stay. Requiring a token is the right posture for anything listening on a socket, standalone included — a tokenless default is how an open endpoint happens. .env.example already documents openssl rand -hex 24.

BREAKING CHANGE

Deployments needing a proxy, an external network or extra mounts must supply their own overlay and pass both -f flags:

docker compose -f docker-compose.yml -f /path/to/your-deployment.yml up -d mcp

Relative paths inside the overlay resolve against the base file's directory, not the overlay's.

Verification

  • base + a deployment overlay reproduces the previous production config byte-identically (captured before, diffed after)
  • docker compose config mcp on the base alone: no external network, every volume source inside the repo
  • no deployment specifics left in tracked files — only example domains in docs and the repo's own check-no-internal-refs.sh pattern; that guard still passes
  • tsc --noEmit clean, 275 tests pass

End-to-end in a fresh clone with no sibling files, on branch #7's code: crawled 25 pages of a real site in 46s with 0 errors, generated all reports, ran the MCP server with auth enabled, and completed a full MCP handshake — initialize, tools/list (crawl, get_report, list_reports), prompts/list (seo-consultant-marek), and prompts/get returning the persona with the audit report injected. No external network, nothing attached to agentic-ops.

🤖 Generated with Claude Code

…yment tree

PR #7 made app/test/typecheck work in a clean clone. The mcp service still could
not start there: it attaches to the external agentic-ops network (start fails with
"Could not attach to network … not found"), pins NODE_ENV=production against an
empty SEO_MCP_TOKEN from the template (the server refuses to boot), and bind-mounts
a sibling Hugo checkout that Docker then auto-creates as a root-owned directory next
to the clone.

Compose merges overlays additively, so a volume or network in the base file cannot
be removed by one — only parameterised. The frontend mount source therefore becomes
${SEO_FRONTEND_DIR:-…}, defaulting to the production path so a deployment whose .env
never heard of the variable is unaffected. `docker compose config mcp` on the base
file alone is byte-identical before and after this commit.

The overlay is named docker-compose.standalone.yml and must be passed explicitly,
rather than following the docker-compose.override.yml convention used by sibling
projects. That filename is auto-loaded by any bare `docker compose up`, so once such
a file existed in this repo, a deploy that forgot to exclude it would silently switch
NODE_ENV to development — which turns the MCP auth guard off and publishes an
unauthenticated endpoint. Requiring both -f flags makes that impossible by accident.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Review of #8: seo-tools is a standalone application and a public repository, so
its compose file should describe the app and nothing about where it runs. The
previous shape had it backwards — the lkv deployment was the base configuration
and docker-compose.standalone.yml was an escape hatch from it, including a
frontend mount defaulting to a sibling website checkout.

Inverting it is also the only layering Compose supports cleanly: overlays merge
additively, so a base file's network or volume can never be subtracted by one,
only added to. Base as the generic app and deployment as the overlay composes
naturally; the reverse needed the ${VAR:-…} parameterisation this removes.

Dropped from the mcp service: ../.env.shared, the VIRTUAL_*/LETSENCRYPT_HOST/
HTTPS_METHOD proxy labels, and the external agentic-ops network. The frontend
mount now defaults to ./storage/frontend inside the repo. Comments that narrated
one particular VPS were rewritten to state the constraint rather than the host —
"only a trusted reverse proxy may set X-Forwarded-For" survives, the topology
does not. docker-compose.standalone.yml is deleted; with the base standalone it
has nothing left to override.

NODE_ENV=production and the mandatory SEO_MCP_TOKEN stay. Requiring a token is
the right posture for anything listening on a socket, standalone included; a
tokenless default is how an open endpoint happens.

BREAKING CHANGE: deployments that need a reverse proxy, an external network or
extra mounts must now supply their own compose overlay and pass both -f flags.
Verified that base + such an overlay reproduces the previous production config
byte-identically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@siva01c siva01c changed the title feat(compose): add a standalone overlay so mcp runs outside the deployment tree refactor(compose)!: make seo-tools a standalone application Sep 1, 2026
@siva01c
siva01c merged commit 2ada977 into fix/sandbox-standalone-findings Sep 1, 2026
4 checks passed
siva01c added a commit that referenced this pull request Sep 1, 2026
chore: land PR #8 into main (stacked merge landed on the wrong base)
@siva01c
siva01c deleted the fix/mcp-standalone branch September 1, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant