Run all nine examples at once with docker compose - #12
Merged
Merged
Conversation
added 6 commits
September 11, 2026 21:02
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.
docker compose up --buildbuilds all nine examples into their own images and runs them behindone nginx on
http://localhost:8080/. That is the whole prerequisite list — no Go, Node, PHP,Python, Ruby, JDK, Rust, .NET or yarn to install.
The part that is worth more than the convenience
Every example ships a
deploy/nginx.conf, they are written to "share a single server block", theygo out in the release archives and the READMEs tell people to install them — and not one of them
had ever been executed. This mounts all nine unchanged and routes the demo through them.
It found two real bugs on the first run. Both are fixed here:
nextjs/deploy/nginx.confredirected into an infinite loop. Next normalises the opposite wayfrom the other eight: with
trailingSlashat its default,{prefix}/is answered with a 308 to{prefix}. The snippet redirected{prefix}to{prefix}/, so following it bounces forever.Anyone who installed that file as documented got a payment page that never loads. Separate
commit,
a32a5b0.rust-axum-jsdid not build on the Rust it claims.src/main.rsused a let chain, whichis unstable before 1.88, while
Cargo.tomlsaysrust-version = "1.85"and four documents say"Rust 1.85 or newer". Fixed the code rather than the claim — the two
ifs are nested now, sothe advertised minimum stays 1.85 and nobody's toolchain has to move. Commit
bb2cdc0.The reason CI missed it is fixed too (
060ac2f): therustjob now builds once more on theversion
Cargo.tomldeclares, after everything else has run onstable. Verified on therunner — the job compiles under
rustc 1.98.1and again underrustc 1.85.1.How it is wired
Every app service joins the nginx container's network namespace:
so the snippets' own
proxy_pass http://127.0.0.1:300xis simply true, and they are mountedread-only exactly as they ship. The ordinary alternative — service names and
LISTEN_ADDR=0.0.0.0— would have meant nine second copies drifting quietly away from theoriginals, which is the one thing
shared/exists to prevent. It also keeps each app's shippedLISTEN_ADDR=127.0.0.1default, needs no DNS in nginx, and leaves ports 3000-3008 free.The price, documented in the file: a shared namespace forbids
ports/networks/hostname, anddocker compose restartdoes not work — restart the stack, not one service.Two apps need more than an environment variable, both for reasons already in their own
CLAUDE.md:php-jsgets a compose pool because the shipped one setsclear_env = yesand names every settingas an
env[]line, andnextjsneedsBASE_PATHas a build argument becausenext buildbakesbasePathin.One root
.envand oneprivate_key.pem(PKCS#8) feed all nine.PUBLIC_URLis the nginx origin;each app appends its own
BASE_PATH.Verified, against the QA gateway
All nine, through nginx:
config.jsisno-storeand carries a liveephemeralTicketfrom the gateway in all eight thathave one — so the OAuth signature, the credentials and the gateway call all work in every image.
nextjshas noconfig.jsby design and carries the ticket in its page instead.shared/views/checkout.htmlthrough all eight prefixes,and
styles.cssis byte-identical through all nine.controlin a/resultquery is403in all nine;/resultwith no query serves.aliasblocks in the express, python and ruby snippets really do serve those fourassets off disk — they come back with
Expires, the other six do not.docker/nginx/index.htmlat/lists the nine.This is the first time the .NET example has been started anywhere.
Not done, on purpose
check. Easy to add later as its own workflow.
e2e-tests/is untouched. Pointing it at containers needs the emulator behind the same nginxso
API_URLandSDK_URLkeep one origin, plus a rework ofapps.tsandplaywright.config.ts. Separate task.One more inconsistency found, and fixed
dotnet-aspnetcore-jsanswered405toHEADwhere the other eight answer it like aGET—minimal APIs'
MapGetmaps exactly the verb named. Every GET route now goes through a one-lineGethelper that isMapMethods(…, ["GET", "HEAD"], …); Kestrel drops the body itself, so thehandlers are untouched.
HEADis200across all nine now. Commit330e9fd.Cost
Adding a language now also costs a
Dockerfile, a.dockerignore, a compose service and a mountline. That is written into
CLAUDE.mdbeside the existing "one line insync-shared.sh" rulerather than left to be discovered.