Repository navigation
fix(ui): discover the nginx resolver at run time instead of hard-coding Docker's - #19
Merged
Merged
Conversation
…ng Docker's The UI proxies API prefixes to the studio so the browser stays same-origin, and since 63e6d2f that proxy_pass goes through a variable to force per-request DNS resolution — which requires an explicit `resolver`. That resolver was pinned to 127.0.0.11, Docker's embedded DNS, which does not exist under podman: its aardvark-dns sits on the network gateway (10.89.0.x, varying by network). The failure mode is the worst shape available. nginx starts, the SPA is served normally, and every proxied API call dies with no response at all — including POST /api/Account/PasswordLogin, so the login page renders and simply does nothing. The only clue is "recv() failed (111: Connection refused) while resolving, resolver: 127.0.0.11:53" buried in the UI container's log. Rather than branching on the runtime, ask the network. Any container attached to the project network is handed the correct nameserver by the runtime itself, so a busybox init service reads its own /etc/resolv.conf, substitutes the value into nginx/default.conf, and writes the result to a volume that duplo-ui mounts. There is no docker-vs-podman case to keep in sync, and it stays correct for any runtime added later. It cannot be done inside the UI image: its entrypoint is `nginx` directly — no /docker-entrypoint.d/, no /etc/nginx/templates — and it runs as nonroot 65532, so the usual envsubst-on-a-template trick is unavailable. nginx has never read /etc/resolv.conf for its own `resolver` directive, so substitution has to happen before nginx starts. The init-container shape mirrors init-perms, already in this file. Verified on both runtimes, same image and same compose file: podman nginx resolver = 10.89.0.1 SPA 200, /v1/... 401, login 401 docker nginx resolver = 127.0.0.11 SPA 200, /v1/... 401, login 401 401 is the pass: nginx resolved the upstream and the studio answered with an auth challenge. Before the fix the same calls returned 000 — nginx never reached it. Zero resolver errors in the UI log on either runtime. tests/test-ui-nginx.sh covers it statically, so it runs on any machine with no runtime installed: the conf must not pin a runtime-specific address, the init service must read /etc/resolv.conf rather than a literal, duplo-ui must wait for it via service_completed_successfully, and shell variables in the init command must be written $$ — compose interpolates $VAR in `command:` before the shell sees it, and an interpolated-away variable yields an empty resolver and an nginx that will not load its config at all. Note the volume replaces the whole /etc/nginx/conf.d directory; the image ships only default.conf there, and the test guards that assumption. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
The UI proxies API path prefixes to the studio so the browser stays same-origin. Since
63e6d2fthatproxy_passgoes through a variable to force per-request DNSresolution, which requires an explicit
resolver— and that resolver was pinned to127.0.0.11, Docker's embedded DNS. It does not exist under podman, whoseaardvark-dns sits on the network gateway (
10.89.0.x, varying by network).The failure mode is the worst shape available. nginx starts, the SPA is served
normally, and every proxied API call dies with no response at all — including
POST /api/Account/PasswordLogin, so the login page renders and simply does nothing.The only clue is buried in the UI container's log:
Rather than branching on the runtime, ask the network. Any container attached to
the project network is handed the correct nameserver by the runtime itself, so a
busybox init service reads its own
/etc/resolv.conf, substitutes the value intonginx/default.conf, and writes the result to a volumeduplo-uimounts. There is nodocker-vs-podman case to keep in sync, and it stays correct for any runtime added
later.
It cannot be done inside the UI image: its entrypoint is
nginxdirectly — no/docker-entrypoint.d/, no/etc/nginx/templates— and it runs as nonroot65532, sothe usual envsubst-on-a-template trick is unavailable. nginx has never read
/etc/resolv.conffor its ownresolverdirective, so substitution has to happenbefore nginx starts. The init-container shape mirrors
init-perms, already in thisfile.
Based on
fix/ui-nginx-port-8080, notdev— deliberately. The resolver fix isuntestable until nginx can start at all, and on
devit cannot: the nonroot UI imagecannot bind port 80. Please merge that one first; this PR's diff reduces to its single
commit once it lands.
1 commit · 3 files · +101 / −5 · new:
tests/test-ui-nginx.shHow it was verified
./run.sh --reset, then built and deployed a sample end to end./scripts/build-extension.shon the affected sample, then deployed it and created one in the UInpm ci && npm run buildin the affectedfrontend/./stop.sh --wipe && ./run.shfrom a clean slate/duplo-extensionand confirmed the guidance still matches realityBox 4 is the row
CONTRIBUTING.mdmaps todocker-compose.yml/nginx/, which iswhat this PR touches. Run on Docker via
stop.sh/run.sh, and on podman viacompose down -v/compose up -d— this branch predates the runtime abstraction, soits
run.shis Docker-only. Nofrontend/is touched, so the npm box is n/a.Output
401is the pass: nginx resolved the upstream, reached the studio, and got an authchallenge.
000was the failure — nginx never reached it. Same image, same composefile, correct address discovered on both runtimes with no branching.
The clean-slate run also confirms
stop.sh --wiperemoves the newnginx_confvolume,so the init container renders into a fresh one rather than inheriting a stale
default.confthat would mask a rendering failure.tests/test-ui-nginx.shis static, so it runs on any machine with no runtimeinstalled: the conf must not pin a runtime-specific address, the init service must read
/etc/resolv.confrather than a literal,duplo-uimust wait for it viaservice_completed_successfully, and shell variables in the init command must bewritten
$$— compose interpolates$VARincommand:before the shell sees it, andan interpolated-away variable yields an empty resolver and an nginx that will not load
its config at all. That last check was mutation-tested (
$$R→$R; confirmed thetest fails, then restored).
Note the volume replaces the whole
/etc/nginx/conf.ddirectory. The image ships onlydefault.confthere, and the test guards that assumption.Blast radius
.env.example— image tags or ports here are adopted into every user's.envon their next./run.shafter an upgrade. Say so plainly above.scripts/upgrade_dev_kit.shorscripts/init-project.sh— the adoption path.github/workflows/Checks
.env, tokens, keys, or customer identifiers in the diffdist/,node_modules/,**/bin/,**/obj/,backend/sdk-packages/ng-common-libtarball was added or bumped, itsNOTICEsits beside it — n/a, none added