Add headless multi-stage Dockerfile.headless - #1
Merged
Conversation
Adds a non-interactive Dockerfile that clones and builds engine, content, and webclient from their gamenight branches, bakes the webclient build into engine/public/client/client.js, and boots straight into the server process (src/app.ts by default) with no setup wizard prompt. Content and Client-TS builds pull in each fork's customizations (e.g. issue LostCityRS#6's camera zoom) rather than relying on whatever was pre-committed to engine. Implements cboyd10/runescape#9
Owner
Author
|
Status: parked mid-investigation by an unattended pickup — see cboyd10/runescape#9 for the full writeup and the self-downgrade (afk -> hitl) it triggered.
|
The build-stage engine build used `bun run build`, which shells out to
`tsx` under the hood; tsx's CLI shim fails to resolve its own relative
import under Bun (`Cannot find module './cjs/index.cjs'`). Invoke the
same script directly through Bun's native TypeScript runtime instead
(`bun run tools/pack/Build.ts`), bypassing the broken shim.
The runtime stage used Bun (`oven/bun:1-slim`), but engine/src/db/query.ts
has a top-level, unconditional `import { DatabaseSync } from 'node:sqlite'`,
a Node builtin Bun 1.3.14 doesn't implement. This crashes module load
regardless of DB_BACKEND. Switch the runtime stage to node:lts-slim,
matching the existing interactive Dockerfile's precedent, and run via
`npx tsx`.
With no data/config/world.json present, the engine falls back to dev
defaults (production: false, build.liveReload: true), which spins up a
DevThread file watcher that crashes on `../content/maps` (EACCES) since
that directory isn't present in the runtime image. Bake in a minimal
world.json with node.production: true to disable it, matching the
headless/production intent of this image.
Verified with real `docker build` and `docker run` against this exact
Dockerfile: the image builds end to end and boots the world with no
crash and no interactive prompt.
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.
Summary
Adds
Dockerfile.headless, a non-interactive multi-stage build for Kubernetes deployment (issue LostCityRS#11's three pod containers), replacing the existing interactive-setup-wizardDockerfilefor that use case. The existingDockerfileis untouched.Changes
Dockerfile.headless: build stage (Bun) clonesengine,content, andwebclient(Client-TS) from theirgamenightbranches oncboyd10, builds webclient first and copies its output intoengine/public/client/client.js(so fork customizations like issue start.ts support for 377-wip LostCityRS/Server#6's camera zoom are always baked in), then builds engine viabun run tools/pack/Build.tsdirectly (not thebun run buildpackage-script alias — see Verification below for why). Runtime stage isnode:lts-slim(not Bun — see below), installs the builtengine/tree's production deps plustsx, bakes in a minimaldata/config/world.json(node.production: true), runs as a non-rootrunneruser, and boots straight intosrc/app.tsvianpx tsx— the three pod roles (engine/login/logger) overrideCMDper container.Verification (real
docker build/docker run, not just reading code)Both ACs pass against the current commit:
docker build -f Dockerfile.headless -t lostcity-test .succeeds end to end.docker run --rm -e DB_BACKEND=sqlite -p 8888:8888 lostcity-testboots straight into the engine process ("World ready: Visit http://localhost:8888/rs2.cgi") with no interactive prompt, and stays up with no crash.Three issues surfaced during verification, all fixed in this PR:
bun run buildfails under Bun (build stage): the engine'sbuildpackage-script istsx tools/pack/Build.ts;tsx's CLI shim fails to resolve its own relative import under Bun (error: Cannot find module './cjs/index.cjs'). Fixed by invokingbun run tools/pack/Build.tsdirectly, bypassing the tsx shim, with identical output.node:sqliteunsupported by Bun (runtime stage):engine/src/db/query.tshas a top-level, unconditionalimport { DatabaseSync } from 'node:sqlite', a Node builtin Bun 1.3.14 doesn't implement (error: No such built-in module: node:sqlite). This crashes module load for all three pod entrypoints regardless ofDB_BACKEND(confirmed with bothsqliteandmysql) — not a narrow sqlite-only edge case. Fixed by switching only the runtime stage tonode:lts-slim+npx tsx, matching the existing interactiveDockerfile's precedent (it already usesnode:lts-slim, not Bun, at runtime). Keeps the fix entirely within this repo/PR rather than requiring a change in theenginesubmodule.world.json(runtime stage): with nodata/config/world.jsonpresent,loadWorldConfig()falls back to dev-oriented defaults (node.production: false,build.liveReload: true), which spins up aDevThreadfile watcher that crashes withEACCES: permission denied, mkdir '../content/maps'(that directory isn't present in the runtime image — onlyengine/is copied in). Fixed by baking in a minimaldata/config/world.jsonwithnode.production: true, which gates offDevThreadcreation (World.ts:if (!Environment.node.production && Environment.build.liveReload)).Also note: the issue's exact AC2 command sets
-e DB_BACKEND=sqlite, but the engine's config now comes entirely fromdata/config/world.json(introduced infeat: Support new world.json config) —Environment.tsdoesn't readprocess.envdirectly anywhere, so that env var is currently a no-op. Harmless here sinceworld.json's defaultdb.backendis already'sqlite', so AC2 passes as written either way; flagging in caseDB_BACKENDis expected to be wired through for issue LostCityRS#11's pod configuration.Notes on the issue's Implementation Notes
The issue flagged a likely
data/config/world.jsonbuild-stage dependency (bun run tools/server/setup.ts, generating and COPYing the file into the build stage). Traced this:tools/pack/Build.tsnever touchesWorldConfigat all, so no config file is needed for the build to succeed — the verbatim build stage works as-is. However, aworld.jsonis needed in the runtime stage, for the different reason above (disabling the dev file-watcher), which is what this PR adds. (Also: the issue namedtools/server/setup.ts; the actual script issrc/setup.ts, invoked vianpm run setup— harmless naming slip, not otherwise relevant since this path isn't exercised by the build.)Meta-repo submodule pointer
Per this project's established submodule PR cascade (see
cboyd10/runescape's.claude/context/CONTEXT.md, "Submodule PR cascade" — used for issues #3/#4/LostCityRS#7): this PR does not close cboyd10/runescape#9 directly. Once this merges, a follow-up PR againstcboyd10/runescapebumping theserversubmodule pointer to this PR's merge commit is needed, and that PR carriesCloses #9. Merging this PR alone would not update the meta-repo's submodule pointer.Implements cboyd10/runescape#9