Where: Dockerfile (both stages) and .dockerignore.
What's wrong: every other part of this repo's tooling is bun-first:
bun.lock is the lockfile actually kept in sync with package.json,
every CI workflow (ci.yml, release.yml, security-audit.yml) installs
via bun install --frozen-lockfile, package.json's engines field lists
bun >=1.0.0, and README.md/docs/SETUP.md instruct contributors to use
bun install/bun run dev exclusively. But the Dockerfile — the thing
that actually produces the artifact deployed to staging/production per
deploy.yml — does this instead:
# builder stage
COPY package*.json ./
RUN npm ci --ignore-scripts
...
RUN npm run build
# production stage
COPY package*.json ./
RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force
npm ci resolves and installs strictly from package-lock.json, not
bun.lock. And .dockerignore makes this explicit rather than
accidental — it lists bun.lock among the excluded files, so it isn't
even available in the build context for the Docker build to use.
Impact: this means the dependency versions actually running in the
deployed production container come from package-lock.json — a file that
(per the separately-filed CI/npm-audit issue) no automated process ever
regenerates or validates against package.json/bun.lock, since every CI
job installs via bun install instead. In other words: CI builds and
tests the application against one set of resolved dependency versions
(bun.lock), while the artifact that's actually shipped and run in
production is built against a different, independently-drifting set
(package-lock.json) — the exact "what we tested isn't what we shipped"
gap that lockfiles exist to prevent. A patch/minor version difference in
even one transitive dependency between the two lockfiles means CI's green
checkmark doesn't actually vouch for the container that goes to
production.
Suggested fix: build the Docker image with bun (RUN bun install --frozen-lockfile and bun run build, using a bun base image or
installing bun in the node:20-alpine stage) so the same lockfile that CI
validates against is the one that ships, and remove bun.lock from
.dockerignore.
Where:
Dockerfile(both stages) and.dockerignore.What's wrong: every other part of this repo's tooling is bun-first:
bun.lockis the lockfile actually kept in sync withpackage.json,every CI workflow (
ci.yml,release.yml,security-audit.yml) installsvia
bun install --frozen-lockfile,package.json'senginesfield listsbun >=1.0.0, andREADME.md/docs/SETUP.mdinstruct contributors to usebun install/bun run devexclusively. But theDockerfile— the thingthat actually produces the artifact deployed to staging/production per
deploy.yml— does this instead:npm ciresolves and installs strictly frompackage-lock.json, notbun.lock. And.dockerignoremakes this explicit rather thanaccidental — it lists
bun.lockamong the excluded files, so it isn'teven available in the build context for the Docker build to use.
Impact: this means the dependency versions actually running in the
deployed production container come from
package-lock.json— a file that(per the separately-filed CI/npm-audit issue) no automated process ever
regenerates or validates against
package.json/bun.lock, since every CIjob installs via
bun installinstead. In other words: CI builds andtests the application against one set of resolved dependency versions
(
bun.lock), while the artifact that's actually shipped and run inproduction is built against a different, independently-drifting set
(
package-lock.json) — the exact "what we tested isn't what we shipped"gap that lockfiles exist to prevent. A patch/minor version difference in
even one transitive dependency between the two lockfiles means CI's green
checkmark doesn't actually vouch for the container that goes to
production.
Suggested fix: build the Docker image with bun (
RUN bun install --frozen-lockfileandbun run build, using a bun base image orinstalling bun in the
node:20-alpinestage) so the same lockfile that CIvalidates against is the one that ships, and remove
bun.lockfrom.dockerignore.