docs(deploy): tell Docker users to browse 127.0.0.1, not localhost - #6325
docs(deploy): tell Docker users to browse 127.0.0.1, not localhost#6325maxmilian wants to merge 1 commit into
Conversation
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
|
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 One quick PR-body request before pool review: could you reshape this into explicit Related: #6263 (original issue context), #6302 (the UI half of the same fix). |
nettee
left a comment
There was a problem hiding this comment.
@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.
|
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 the PR body is now reshaped into the requested sections — Two things worth calling out in the rewrite, since both are places where I tightened a claim rather than restructured one:
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. |
|
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. |
|
Marked ready for review, per your note on #6263:
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 |
|
@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. |
|
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. |
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.mdend to end on a fresh Docker install. Step 6 tells you to openhttp://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/apicalls answered with403 {"error":"Powered preview origin cannot access this API route"}— andlistProjectsswallows 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.1for thecurlcheck, so the doc switches loopback names between the verification step and the browser step, and only the browser step is affected.curland MCP clients send nosec-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 theHostheader as the only variable:Host: localhostHost: 127.0.0.1127.0.0.1—docker-compose.linux.yml0.0.0.0—docker-compose.yml(default)reportHostForPoweredPreview()maps0.0.0.0to127.0.0.1, sopoweredPreviewHost()returnslocalhostunder 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
http://127.0.0.1:7456/in Step 6, consistent with thecurlin Step 5, with a one-line note on whylocalhostis the reserved name here. Following the doc verbatim now lands on a working project list instead of an empty one.curlreturns 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 coverednetwork_mode: hostand the CLI mounts but never said the URL.No behavior change: docs only, nothing shipped or executed changes.
Surface area
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
mainand 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
upstream/main(517f39a) with theHostheader as the only variable, for both daemon bind hosts — the 403 / 200 matrix under Why.Host: localhostis rejected with{"error":"Powered preview origin cannot access this API route"}in every combination;Host: 127.0.0.1returns 200.reportHostForPoweredPreview()→poweredPreviewHost()to confirm0.0.0.0(the defaultdocker-compose.ymlbind) resolves to the same reservedlocalhost, so the default Compose path is affected identically — the reason this edits the shared Step 6 instead of adding a Linux-only aside.curlexample so the walkthrough no longer switches loopback names mid-flow.pnpm guard/pnpm typecheckrun: 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_HOSTis127.0.0.1(apps/daemon/src/daemon-startup.ts:31), solocalhost:7456is the reserved name for every install method, not only Docker — andQUICKSTART.md,README.mdand their translations all point users atlocalhost: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).