|
|
|
@@ -1,11 +1,13 @@
|
|
|
|
|
---
|
|
|
|
|
type: knowledge
|
|
|
|
|
summary: Deployment — multi-stage Bun Alpine Dockerfile; install through pnpm (git+node inside the image) but bun at runtime; runs `bun run start` from src/ (not dist/), EXPOSE 3000, env PORT/NODE_ENV; no CI/CD committed; dev goes through the portless wrapper
|
|
|
|
|
last_checked: 2026-07-14
|
|
|
|
|
summary: Deployment — multi-stage Bun Alpine Dockerfile; install through pnpm (git+node inside the image) but bun at runtime; runs `bun run start` from src/ (not dist/), EXPOSE 3000; the data-layer git dependency must be pinned to a tag/commit and match `contracts.yaml`'s ref; the shared wallet reaches the container through env vars, not a mount, because it isn't a secret; no CI/CD committed; dev goes through the portless wrapper
|
|
|
|
|
last_checked: 2026-08-17
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# Deployment & infra
|
|
|
|
|
|
|
|
|
|
Nothing has actually been deployed with this shape yet — this leaf states what a deployment needs to line up, verified against the code and manifests, not a procedure that has been run end to end.
|
|
|
|
|
|
|
|
|
|
## Dockerfile
|
|
|
|
|
|
|
|
|
|
A `Dockerfile` exists (multi-stage Bun Alpine). **Installation goes through pnpm, but runtime/build/test stay on bun** (see [[knowledge_stack-and-commands]]):
|
|
|
|
@@ -14,16 +16,41 @@ A `Dockerfile` exists (multi-stage Bun Alpine). **Installation goes through pnpm
|
|
|
|
|
|
|
|
|
|
**`bun` peer pitfall**: `bun-plugin-tailwind` declares `bun` as a peerDependency → pnpm materializes the npm `bun` package and **creates a `node_modules/.bin/bun` shim** that shadows the `bun` from the PATH under `bun run`/`pnpm run`. Its postinstall is ignored by default → broken shim → `bun run start` fails. Fixed by approving the build: `pnpm.onlyBuiltDependencies: ["bun"]` in `package.json` (the postinstall then downloads the real binary). Without that, the whole pnpm migration breaks startup.
|
|
|
|
|
|
|
|
|
|
**`tailwindcss` is a devDependency the server needs at serve time, not only at build time.** `bunfig.toml`'s `[serve.static] plugins = ["bun-plugin-tailwind"]` applies to `Bun.serve`'s HTML-import serving — the path both `bun run dev` and `bun run start` use ([[knowledge_build-pipeline]]) — not only to `bun run build.ts`. The install stage must therefore keep installing devDependencies: no `--prod`, and `NODE_ENV` stays unset until the `release` stage, after `pnpm install --frozen-lockfile` has already run. Moving `ENV NODE_ENV=production` earlier, or adding `--prod` to the install, would drop `tailwindcss` and break every serve, dev included.
|
|
|
|
|
|
|
|
|
|
**Production runs the sources, and this is the normal path, not a quirk**: `start` = `NODE_ENV=production bun src/index.ts` → the container **runs the TypeScript directly** (Bun transpiles on the fly). `bun run build` (→ `dist/`) is on **no** path at all — nothing serves that directory, in this container or anywhere else; serving it would mean changing the entrypoint. Consequence for the code: in this deployment `NODE_ENV=production` says *how* the sources run, never *that they were bundled* — [[knowledge_build-pipeline]].
|
|
|
|
|
|
|
|
|
|
## The data-layer git dependency must stay pinned, and the pin must be checkable
|
|
|
|
|
|
|
|
|
|
`package.json` resolves `@ng-eventually/polyfill` from `git+https://…/ng-eventually.git#<ref>&path:/packages/polyfill` — the `path:` selector is what lets a subdirectory of the provider's repo be installed as the package. Two things follow, ahead of any real deployment:
|
|
|
|
|
|
|
|
|
|
- **`<ref>` must name a tag or a commit, never a branch.** A branch moves: the image was built against whatever commit the branch pointed to at build time, and the branch head can advance afterwards without the image changing — so "the same deployment" silently starts drifting from what it was actually built against. The tag-naming convention itself is the provider's call and is not settled yet; the requirement is only that the ref be immutable.
|
|
|
|
|
- **The same `<ref>` should also be the `ref:` of the `polyfill-surface` entry in `.project/contracts.yaml`.** That manifest pins the version of [[contract_polyfill-surface]] the app is coded against; when it names the same ref as `package.json`'s specifier, the contract the app was written for and the package actually installed name the same state, and a difference between the two becomes visible instead of silent. Both now name the **same commit**, which is the state a deployment can ship on. A tag is expected to replace that commit once the provider settles a naming convention — a one-line change in each of the two files, with the invariant unchanged: whatever the ref is, the two must agree.
|
|
|
|
|
|
|
|
|
|
**`pnpm install --frozen-lockfile` (the Dockerfile's install step) never regenerates — it only verifies.** `pnpm-lock.yaml` must already reproduce `package.json` exactly, so any change to the git specifier (ref, path, or package name) needs `pnpm install` run and the regenerated lockfile committed *before* the image can build; skipping that step fails the build outright, not silently. This has bitten once: the lockfile still named the old package and path after the dependency was renamed, so `--frozen-lockfile` refused and the image could not build until it was regenerated.
|
|
|
|
|
|
|
|
|
|
## CI/CD
|
|
|
|
|
|
|
|
|
|
**No** pipeline is committed (`.github/workflows/` absent, no Coolify config in the repo). A knowingly accepted blind spot. To host the Bun app, the `coolify-hosting` skill applies.
|
|
|
|
|
|
|
|
|
|
Also untested: whether a **deployed origin** can be embedded in the hosted broker's iframe at all — the SDK's identity flow runs the app inside that iframe (see [[contract_polyfill-surface]]'s `ensureIdentity()` barrier). [[caveat_firefox-lna-blocks-broker-iframe]] records the one failure mode known here, and it is a **local-dev-origin** one (`127.0.0.1` blocked by Firefox LNA); no scenario in this repo exercises a real deployed origin against the broker's embedding policy.
|
|
|
|
|
|
|
|
|
|
## Environment variables
|
|
|
|
|
|
|
|
|
|
- `PORT` (default 3000), `NODE_ENV` (enables/disables HMR and the dev auto-seed — see concept `data-layer`).
|
|
|
|
|
- No `.env*` is committed (`.env` is gitignored). No secret management in the repo.
|
|
|
|
|
- No `.env*` is committed (`.env` is gitignored).
|
|
|
|
|
|
|
|
|
|
### The shared wallet: config, not a secret, not a mount
|
|
|
|
|
|
|
|
|
|
[[contract_polyfill-surface]] requires the app to serve a wallet file (`.ngw`) and pass its URL and password to `configure` as `sharedWallet: { fileUrl, password }`. `*.ngw` is gitignored and no deployment mounts one, so `src/index.ts` serves it from environment variables, read fresh on every request:
|
|
|
|
|
|
|
|
|
|
- `FESTIPOD_SHARED_WALLET_PASSWORD` — the password, always read this way (dev, tests, and deployments alike).
|
|
|
|
|
- `FESTIPOD_SHARED_WALLET_FILE` — a filesystem path to the `.ngw` file. The form local dev and the test harness use: the file sits on the machine's disk.
|
|
|
|
|
- `FESTIPOD_SHARED_WALLET_FILE_BASE64` — the file's bytes, base64-encoded. The form a deployment uses instead, since nothing mounts a `.ngw` into the container.
|
|
|
|
|
|
|
|
|
|
**Precedence is one-directional and does not fall through.** `FESTIPOD_SHARED_WALLET_FILE` wins whenever it is set, *even if the path turns out unreadable* — an unreadable path answers 404, it does **not** fall back to the base64 form. A deployment must set exactly one of the two; leaving a leftover `FESTIPOD_SHARED_WALLET_FILE` pointing nowhere in a deployment environment silently 404s instead of serving the base64 value that was actually intended. A malformed base64 value answers 500 naming the variable — never a 404, which would be indistinguishable from "not configured at all".
|
|
|
|
|
|
|
|
|
|
**Neither the password nor the wallet file is a secret**, and that is deliberate, not an oversight: the contract has the app hand both to every user who opens it — that is how a first-time device without its own wallet onboards. Provisioning them as protected/mounted storage would guard something the app already gives away by design; they travel as plain configuration instead, and a new host needs only its environment variables, nothing to mount.
|
|
|
|
|
|
|
|
|
|
## Dev
|
|
|
|
|
|
|
|
|
|