Skip to content

docs(deploy): tell Docker users to browse 127.0.0.1, not localhost - #6325

Closed
maxmilian wants to merge 1 commit into
nexu-io:mainfrom
maxmilian:docs-docker-loopback-host-url
Closed

docs(deploy): tell Docker users to browse 127.0.0.1, not localhost#6325
maxmilian wants to merge 1 commit into
nexu-io:mainfrom
maxmilian:docs-docker-loopback-host-url

Conversation

@maxmilian

@maxmilian maxmilian commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Part of #6263 — this is the docs half, split off as agreed in this comment. #6302 covers the UI half (surfacing the blocked-origin error instead of the empty state), so neither PR closes the issue on its own.

Why

I hit this myself while following docs/deployment/docker.md end to end on a fresh Docker install. Step 6 tells you to open http://localhost:7456/, and that is precisely the URL the origin guard rejects: the daemon reserves whichever loopback name it is not bound to as the powered-preview origin, so a browser tab on that name has its /api calls answered with 403 {"error":"Powered preview origin cannot access this API route"} — and listProjects swallows that into an empty project list.

The pain is that the walkthrough itself walks users into the failure. Step 5 already uses 127.0.0.1 for the curl check, so the doc switches loopback names between the verification step and the browser step, and only the browser step is affected. curl and MCP clients send no sec-fetch-* headers and pass the guard either way — which is exactly why this reads as "Docker install produced no projects" rather than "my request was blocked".

It is not Linux-specific. Worth flagging, because #6263 was filed as a Linux Docker report and I assumed the same when I picked it up. Measured against the guard on main (517f39a) with the Host header as the only variable:

daemon bind host Host: localhost Host: 127.0.0.1
127.0.0.1docker-compose.linux.yml 403 200
0.0.0.0docker-compose.yml (default) 403 200

reportHostForPoweredPreview() maps 0.0.0.0 to 127.0.0.1, so poweredPreviewHost() returns localhost under the default Compose file as well. Both paths land in the same place, which is why this fixes the shared Step 6 rather than adding a Linux-only aside.

What users will see

  • The Docker walkthrough now sends you to http://127.0.0.1:7456/ in Step 6, consistent with the curl in Step 5, with a one-line note on why localhost is the reserved name here. Following the doc verbatim now lands on a working project list instead of an empty one.
  • A new Common Issues entry keyed to the observable symptom — "empty project list in the browser while curl returns real projects" — that names the 403 and the DevTools check, so users who already hit it can search their way out.
  • deploy/README.md's Linux host-networking section now states which browser URL to open. It previously covered network_mode: host and the CLI mounts but never said the URL.

No behavior change: docs only, nothing shipped or executed changes.

Surface area

  • UI
  • Keyboard shortcut
  • CLI / env var
  • API / contract
  • Extension point
  • i18n keys
  • New top-level dependency
  • Default behavior change
  • None — docs only (docs/deployment/docker.md, deploy/README.md; +15 / −1)

Screenshots

N/A — no UI surface touched.

Bug fix verification

No red spec: the defect is in prose, so there is nothing in the test suite that can go red on main and green here. What I did instead is the probe described under Validation — it establishes that the URL the doc tells users to open is the one the guard rejects, which is the whole claim this PR rests on.

To be precise about scope: I verified the guard's behavior, not a full end-to-end Docker run. The browser-side symptom (empty project list) is the report in #6263; what I added is the measurement showing which loopback name gets the 403 and that it is not Linux-specific.

Validation

  • Drove the guard directly on a worktree at upstream/main (517f39a) with the Host header as the only variable, for both daemon bind hosts — the 403 / 200 matrix under Why. Host: localhost is rejected with {"error":"Powered preview origin cannot access this API route"} in every combination; Host: 127.0.0.1 returns 200.
  • Traced reportHostForPoweredPreview()poweredPreviewHost() to confirm 0.0.0.0 (the default docker-compose.yml bind) resolves to the same reserved localhost, so the default Compose path is affected identically — the reason this edits the shared Step 6 instead of adding a Linux-only aside.
  • Cross-checked the two edited files against Step 5's existing curl example so the walkthrough no longer switches loopback names mid-flow.
  • No pnpm guard / pnpm typecheck run: the branch changes two Markdown files (+15 / −1) and touches nothing those checks cover. Happy to run them if you'd rather have the clean output on record.

One thing I did not change, deliberately

DEFAULT_DAEMON_BIND_HOST is 127.0.0.1 (apps/daemon/src/daemon-startup.ts:31), so localhost:7456 is the reserved name for every install method, not only Docker — and QUICKSTART.md, README.md and their translations all point users at localhost:7456. I have not touched those: it is a much wider change than the docs half of this issue, the non-Docker flows normally auto-open the correct URL so users rarely type the name by hand, and I would rather you decide whether the answer there is a docs sweep or making the guard tolerate both loopback spellings for first-party origins. Happy to do either as a follow-up.

Related: #6263 (original issue context), #6302 (the UI half of the same fix).

The Docker walkthrough's Step 6 sends users to http://localhost:7456. That is
the one loopback name the daemon reserves for powered previews whenever it is
bound to 127.0.0.1, so the browser's /api calls come back 403 'Powered preview
origin cannot access this API route' and the front end renders the swallowed
failure as an empty project list. curl and MCP clients send no sec-fetch-*
headers and are unaffected, which is why it reads as missing data rather than a
blocked request.

Measured against the guard on main, with the Host header as the only variable:

  bind 127.0.0.1 (docker-compose.linux.yml)  Host: localhost -> 403, 127.0.0.1 -> 200
  bind 0.0.0.0    (docker-compose.yml)       Host: localhost -> 403, 127.0.0.1 -> 200

So this is not Linux-specific as originally reported: reportHostForPoweredPreview()
maps 0.0.0.0 to 127.0.0.1, which leaves localhost reserved under the default
Compose file too.

Points Step 6 at 127.0.0.1 (matching the curl in Step 5), explains why, adds a
symptom-keyed entry to Common Issues, and notes the browser URL in the Linux
host-networking section of deploy/README.md.

Part of nexu-io#6263
@lefarcen

lefarcen commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Thanks @maxmilian — nice split here. Keeping the docs half separate from #6302 makes the scope easy to follow, and the write-up already makes the localhost vs 127.0.0.1 behavior very clear.

One quick PR-body request before pool review: could you reshape this into explicit Why, What users will see, Surface area, and Validation sections? Most of the substance is already in the description, so this should mainly be a small restructure plus a short note on what you verified.

Related: #6263 (original issue context), #6302 (the UI half of the same fix).

@nettee nettee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxmilian I verified the updated Docker browser URL guidance against the current daemon origin-guard logic and the compose defaults. The changed docs now consistently point Docker users at http://127.0.0.1:7456/, explain why localhost is reserved for powered previews in this setup, and add a useful symptom-based troubleshooting note for the empty project list case. Nice cleanup on a user-facing footgun.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen

lefarcen commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Thanks @maxmilian@nettee has approved the current head, and I don't have anything additional to add from the bot side on this docs-only split.

At this point it's just waiting on the remaining CI checks.

@lefarcen
lefarcen marked this pull request as draft August 4, 2026 13:43
@maxmilian

Copy link
Copy Markdown
Contributor Author

@lefarcen the PR body is now reshaped into the requested sections — Why, What users will see, Surface area, Screenshots, Bug fix verification, Validation — matching the repo template rather than only the four you named, so the pool reviewers get every section they expect. No code changed; head is still d9712eb.

Two things worth calling out in the rewrite, since both are places where I tightened a claim rather than restructured one:

  • Validation is now scoped honestly. The earlier description could be read as "I ran the Docker walkthrough end to end". What I actually did was drive the origin guard on a worktree at upstream/main (517f39a) with the Host header as the only variable, across both daemon bind hosts, and trace reportHostForPoweredPreview()poweredPreviewHost(). That is what produces the 403 / 200 matrix and the "not Linux-specific" conclusion. The browser-side symptom itself is the report in [Bug]: Docker daemon on Linux: visiting http://localhost:7456 shows empty project list (403 from powered-preview origin guard swallowed as "还没有项目") #6263, and the body now says so.
  • Bug fix verification states plainly that there is no red spec, because the defect is in prose and nothing in the suite can go red on main for it — with the probe named as what stands in its place, per the AGENTS.md bug workflow.

On the state: I noticed the PR was converted back to draft on Aug 4 after @nettee's approval, which also cancelled the check run. I am not going to flip it back to ready myself — last time I did that on #5842 it turned out to be a deliberate park on your side, and I would rather ask than repeat it. If it is parked for a QA or pool-review gate, that is fine and I will leave it alone; if it was meant to be waiting on the body rewrite, that is done now and it can go back to ready whenever suits you. Anything else you want changed before then, happy to do it.

@lefarcen

lefarcen commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Thanks for tightening this up. The body now answers the template ask cleanly, and the narrower validation wording plus the explicit "no red spec" note make the scope much clearer.

I don't have anything else to add on the body from the bot side. Leaving the draft/ready switch alone here makes sense.

@maxmilian
maxmilian marked this pull request as ready for review August 5, 2026 14:40
@maxmilian

Copy link
Copy Markdown
Contributor Author

Marked ready for review, per your note on #6263:

If you're still willing to carry the docs half, please mark #6325 ready for review again when you want it back in the merge path.

Flagging the sequencing so it doesn't read as me overriding your earlier "leaving the draft/ready switch alone here makes sense" — that comment landed at 07:22 and the #6263 one at 07:42, so I'm acting on the later of the two. If I've read the order wrong, convert it back and I'll leave it parked.

Nothing moved on the code: head is still d9712eb, the commit @nettee approved on Aug 1, and the only change since then was the PR body rewrite. So this is the same reviewed +15/−1 docs diff, now back in the merge path.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

@maxmilian friendly reminder: this PR appears to be waiting on author action (failing CI) and has had no human activity for more than 3 days.

When you have a chance, please reply here or push an update. To keep the queue manageable, PRs with no human activity for more than 5 days may be closed automatically, but they can be reopened when work resumes.

@github-actions

Copy link
Copy Markdown
Contributor

Closing this PR for now because it appears to be waiting on author action (failing CI) and has had no human activity for more than 5 days.

This is only a queue-management step, not a rejection of the work. If you would like to continue, please leave a comment or push an update and reopen the PR when ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/low Low risk: docs/i18n/assets only size/XS PR changes <20 lines type/docs Documentation changes only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants