agentleFS
pedroiff0pedroiff0/awesome-skills/skills/devops/docker-single-port-multi-instance/SKILL.md

docker-single-port-multi-instance

Skill1 starsChanged 44 days agoMITView on GitHub

Consolidate multiple Docker Compose app instances (production / test / demo) behind ONE host port using an nginx reverse proxy that routes by URL path prefix (e.g. /demo). Use when a user wants 'one port, several apps/banks' or a demo reachable simultaneously from the main landing page with its own database.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

12 rules. About 0 are common, 3 are distinctive, and 9 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When the ONLY goal is routing separate instances, do NOT refactor the app to hold two DB connections (e.g.

    line 18
  2. Distinctive:

    Keep each instance as its own container (own DB, own env).

    line 23
  3. Distinctive:

    Put ONE nginx container on the public port.

    line 24
  4. Very specific:

    If the demo is served under /demo/* but its client JS calls /api/...

    line 34
  5. Very specific:

    Verify with curl -s -D - -o /dev/null URL/demo | grep -i location — it must show Location: /demo/app (NO :5000), never …

    line 74
  6. Very specific:

    If you see :5000, the config did not take: rebuild the nginx image and docker compose up -d --force-recreate nginx (a plain up may not …

    line 76
  7. Very specific:

    Only nginx publishes the host port.

    line 82
  8. Very specific:

    After you docker compose build app app-demo (or up -d app app-demo) to push a code change, the demo can suddenly break even though the app …

    line 88
  9. Very specific:

    If restart alone doesn't take, rebuild + recreate the image: docker compose build nginx && docker compose up -d --force-recreate nginx.

    line 109
  10. Very specific:

    Do NOT reach for docker compose down -v — that wipes named volumes (e.g.

    line 111
  11. Very specific:

    Call the demo API WITH that cookie: curl -o /dev/null -w '%{http_code}' --cookie "$CK" URL/demo/api/dashboard → expect 200.

    line 123
  12. Very specific:

    If a /demo/* PAGE returns 302 /login, run the stale-routing diagnostic above BEFORE suspecting app code — 9 times out of 10 it's the nginx …

    line 127

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

One port, many instances: nginx reverse proxy by path prefix

When this applies

  • You have an app that runs as several instances: production, test, and a public demo — each with its OWN database.
  • The user asks for a single published port (e.g. 4460) where the demo is reached by clicking a button on the landing page, and the demo's data lives in its own DB, isolated from prod/test.
  • Key constraint: the demo must be reachable SIMULTANEOUSLY with the main app.

Prefer nginx-by-prefix over in-app multi-tenancy

When the ONLY goal is routing separate instances, do NOT refactor the app to hold two DB connections (e.g. a second mongoose connection) just to serve /demo/* from the same process. That touches ~30 files (every model + service + controller) and risks regressing the normal routes. Instead:

  • Keep each instance as its own container (own DB, own env).
  • Put ONE nginx container on the public port; it routes:
    • /demo/* → app-demo (strip prefix: proxy_pass http://app-demo:5000/;)
    • / (rest) → app (the principal: test or prod chosen via .env)
  • The demo container keeps its existing autologin; the button on the landing just points to /demo/app.

This delivers "one port, demo with own DB, simultaneous" with near-zero risk to the main app's logic.

The non-obvious gotcha: APIs must also be prefixed

If the demo is served under /demo/* but its client JS calls /api/... (absolute), the proxy sends those calls to the PRINCIPAL instance, not the demo → tokens fail (401 "Token invalido"). You must route the demo's APIs to the demo instance too:

  1. Mount the demo's API router under /demo/api in the app (only when a DEMO_AUTOLOGIN-style flag is set), in addition to the normal /api.
  2. Make the frontend prefix API calls with /demo when running in the demo. Inject the prefix WITHOUT an inline script (CSP will block it — see the express-csp-runtime-config skill): use data-api-prefix="/demo" on <html> and read it with getAttribute inside the shared fetch wrapper.

nginx location block:

location = /demo { return 302 /demo/app; }
location /demo/ { proxy_pass http://app-demo:5000/; }
location / { proxy_pass http://app:5000; }

CRITICAL: make the /demo redirect relative (absolute_redirect off)

The snippet above is subtly broken in production. When nginx listens on the INTERNAL port (e.g. 5000, mapped to 4460 on the host) and you return 302 /demo/app, nginx builds an ABSOLUTE Location: http://host:5000/demo/app using the listen port — not the port the browser used (4460). The user's browser is then thrown at a port that does not exist outside the host, and /demo appears dead even though /demo/app works directly.

Fix: disable absolute redirects in the server block so the Location stays relative (/demo/app) and the browser resolves it against the real port:

server {
  listen 5000;
  absolute_redirect off;   # keep Location relative -> /demo/app
  port_in_redirect off;    # belt-and-suspenders, drop the port entirely
  location = /demo { return 302 /demo/app; }
  location /demo/ { proxy_pass http://app-demo:5000/; }
  location / { proxy_pass http://app:5000; }
}

Verify with curl -s -D - -o /dev/null URL/demo | grep -i location — it must show Location: /demo/app (NO :5000), never http://host:5000/demo/app. If you see :5000, the config did not take: rebuild the nginx image and docker compose up -d --force-recreate nginx (a plain up may not replace it).

docker-compose shape

  • One file, project name preserved (so named volumes survive, e.g. fa_mongo_data).
  • Services: app, app-demo, mongo, mongo-demo (tmpfs for demo), nginx.
  • Only nginx publishes the host port. app and app-demo have NO host port (internal network only).
  • Demo DB on tmpfs so it resets on restart; seed manually via docker compose exec app-demo node scripts/seed-demo.js.

Pitfall: nginx serves STALE routing after a sibling container rebuild

After you docker compose build app app-demo (or up -d app app-demo) to push a code change, the demo can suddenly break even though the app code is correct: /demo/veiculos (and any /demo/* page) returns 302 -> /login, and the demo API /demo/api/* returns 401. The container app-demo is healthy and answers 200 when hit DIRECTLY on its internal IP:port, but via the published port it fails. Root cause: the nginx container never re-read its default.conf — it kept the in-memory routing from before the rebuild and is sending /demo/* to the PRINCIPAL instance (which has no demo autologin → 302 /login).

Tell-tale sign (this is the key diagnostic): hit /demo/veiculos through the published port, then check BOTH app logs. If neither app nor app-demo logged the request but nginx returned a 302 with the app's CSP headers, the request never reached an app — nginx generated the redirect itself from a stale location/return. (Add a one-line console.log('[mw]', req.path) at the top of the demo autologin middleware to confirm whether the demo instance even saw the request; remove it after.)

Fix (cheapest first):

  1. docker compose restart nginx — forces nginx to re-read default.conf. Re-test /demo/veiculos → should now be 200 and the autologin middleware should log path= / host= <published-host>.
  2. If restart alone doesn't take, rebuild + recreate the image: docker compose build nginx && docker compose up -d --force-recreate nginx. Do NOT reach for docker compose down -v — that wipes named volumes (e.g. fa_mongo_data); the HANDOFF explicitly forbids it.

Why this happens: up -d app app-demo recreates those services but leaves the long-running nginx container untouched, and nginx does not hot-reload its config on a sibling's restart. A plain restart re-reads the (already-correct, baked-into the-image) conf.

Verify server-side (don't trust a caching browser)

The inspection browser may keep stale HTML/JS across navigations and report phantom 401s even after a correct rebuild. Validate at the HTTP layer instead:

  1. Capture the autologin Set-Cookie: CK=$(curl -s -i URL/demo/app | grep -i set-cookie | ...).
  2. Call the demo API WITH that cookie: curl -o /dev/null -w '%{http_code}' --cookie "$CK" URL/demo/api/dashboard → expect 200. The same cookie against /api/dashboard (no prefix) → 401 (hits principal). That pair proves routing + auth are correct.
  3. If a /demo/* PAGE returns 302 /login, run the stale-routing diagnostic above BEFORE suspecting app code — 9 times out of 10 it's the nginx conf not being reloaded.

Reference

See references/nginx-demo-example.md for a concrete financas-app docker-compose.yml + nginx/default.conf + app.js mount snippet.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.