Three repositories have no .dockerignore, so a local docker build fails #75

Open
opened 2026-09-09 11:22:03 +00:00 by ahmad · 1 comment
Owner

Context

docker build fails in ahmad/expiro from any working copy that has run
pnpm install, because the repository has no .dockerignore and the
host's node_modules reaches the build context:

[ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY] Aborted removal of modules
directory due to no TTY

pnpm finds a modules directory it did not create, wants to purge it, and
will not do so without a TTY. The build then fails at
RUN pnpm --filter @expiro/web build.

CI is unaffected, which is exactly why nobody has hit this. A pipeline
checks out a fresh clone with no node_modules, so the context is clean
and the build succeeds. The failure only reaches whoever tries to build or
run the image locally — reproducing a production problem, testing a
Dockerfile change, or checking what nginx actually serves, which is how it
was found (ahmad/expiro#85).

The second cost is size. Without an ignore file the whole node_modules
tree is uploaded to the daemon on every build. ahmad/larda#63 measured
the same context at 170.82 MB against 8.91 kB once ignored.

Surveyed 2026-09-09 through the contents API. Three repositories build
from a root Dockerfile and have no .dockerignore:

Repository .dockerignore
ahmad/expiro missing
Bayan/stem missing
ahmad/launchpad missing
ahmad/larda present (#71)
suhba/khitta present
suhba/suhba-docs present

ahmad/larda#71 is the worked example, and its lesson should travel with
the file rather than be rediscovered: every pattern must be prefixed
**/
. A bare node_modules matches only at the context root, so in a
workspace it misses apps/*/node_modules — which is the case that
matters, and the one that looks fixed until someone checks the image.

Scope

  • In: a .dockerignore in each of the three repositories, patterns
    **/-prefixed, covering at least node_modules, build output, local
    env files and VCS metadata, with !**/.env.example after **/.env.*.
  • In: one pull request per repository.
  • Out: changing any Dockerfile or what the images contain.
  • Out: the repositories that already have one — check the patterns are
    **/-prefixed while there, but only report it, do not widen this issue.

Acceptance criteria

  • Given a working copy that has run pnpm install, when docker build
    runs from the repository root, then it completes.
  • Given the build context, when its size is compared before and after,
    then the reduction is recorded on the pull request.
  • Given the built image, when its node_modules are inspected, then
    they are the ones the build installed and not the host's.
  • Given the .dockerignore, when its patterns are read, then every one
    that can nest is **/-prefixed.
### Context `docker build` fails in `ahmad/expiro` from any working copy that has run `pnpm install`, because the repository has no `.dockerignore` and the host's `node_modules` reaches the build context: ``` [ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY] Aborted removal of modules directory due to no TTY ``` pnpm finds a modules directory it did not create, wants to purge it, and will not do so without a TTY. The build then fails at `RUN pnpm --filter @expiro/web build`. **CI is unaffected, which is exactly why nobody has hit this.** A pipeline checks out a fresh clone with no `node_modules`, so the context is clean and the build succeeds. The failure only reaches whoever tries to build or run the image locally — reproducing a production problem, testing a Dockerfile change, or checking what nginx actually serves, which is how it was found (`ahmad/expiro#85`). The second cost is size. Without an ignore file the whole `node_modules` tree is uploaded to the daemon on every build. `ahmad/larda#63` measured the same context at **170.82 MB** against **8.91 kB** once ignored. Surveyed 2026-09-09 through the contents API. Three repositories build from a root `Dockerfile` and have no `.dockerignore`: | Repository | `.dockerignore` | | --- | --- | | `ahmad/expiro` | **missing** | | `Bayan/stem` | **missing** | | `ahmad/launchpad` | **missing** | | `ahmad/larda` | present (`#71`) | | `suhba/khitta` | present | | `suhba/suhba-docs` | present | `ahmad/larda#71` is the worked example, and its lesson should travel with the file rather than be rediscovered: **every pattern must be prefixed `**/`**. A bare `node_modules` matches only at the context root, so in a workspace it misses `apps/*/node_modules` — which is the case that matters, and the one that looks fixed until someone checks the image. ### Scope - In: a `.dockerignore` in each of the three repositories, patterns `**/`-prefixed, covering at least `node_modules`, build output, local env files and VCS metadata, with `!**/.env.example` after `**/.env.*`. - In: one pull request per repository. - Out: changing any Dockerfile or what the images contain. - Out: the repositories that already have one — check the patterns are `**/`-prefixed while there, but only report it, do not widen this issue. ### Acceptance criteria - [ ] Given a working copy that has run `pnpm install`, when `docker build` runs from the repository root, then it completes. - [ ] Given the build context, when its size is compared before and after, then the reduction is recorded on the pull request. - [ ] Given the built image, when its `node_modules` are inspected, then they are the ones the build installed and not the host's. - [ ] Given the `.dockerignore`, when its patterns are read, then every one that can nest is `**/`-prefixed.
ahmad self-assigned this 2026-09-09 12:14:12 +00:00
Author
Owner

Correcting this issue's central claim before it misleads anyone: only
one of the three actually fails.

The body says docker build fails from a working copy that has run an
install, and generalises that to all three repositories. I wrote that
from one observation on ahmad/expiro and a survey of which repositories
lack the file — not from building the other two. Built now, with an
install already run in each:

Repository Copies docker build Context before → after
ahmad/expiro COPY . . fails — ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY 170.1 MB → 580 kB
Bayan/stem COPY . . after npm ci succeeds 152.9 MB → 290 kB
ahmad/launchpad named paths, not COPY . . succeeds 171.7 MB → 250 kB

Why they differ, since it is the useful part:

  • ahmad/launchpad copies package.json, the lockfiles, the configs and
    then public, scripts, src by name. The host's node_modules
    cannot reach an image layer, so nothing can break. It still uploads the
    whole context, because docker sends the context to the daemon
    regardless of which paths the Dockerfile later copies — which is the
    only cost there, and it is 171.7 MB of it.
  • Bayan/stem does end with COPY . ., but after npm ci, and npm
    has no equivalent of pnpm's abort
    . So the host's modules quietly
    overwrite the installed ones and the build carries on. I looked for the
    consequence — platform-specific @esbuild/* or @rollup/rollup-*
    packages differing between host and image — and found neither present
    in this dependency tree, so there is nothing to demonstrate. The
    overwrite is real; its effect here is latent.
  • Only ahmad/expiro combines COPY . . with pnpm, which refuses to
    proceed against a modules directory it did not create. That is the
    failure, and it is one repository, not three.

The acceptance criteria were written for the wrong premise, so they
are answered as follows rather than ticked uniformly:

  • "when docker build runs, then it completes" — now true everywhere;
    it was already true in two of the three.
  • "the reduction is recorded on the pull request" — done, per the table
    above, each measured on its own repository.
  • "the image's node_modules are the ones the build installed" — proven
    for expiro by pnpm completing at all, and for the other two by a
    COPY . /ctx probe image showing no node_modules reaches the context.
  • "every pattern that can nest is **/-prefixed" — yes in all three, and
    the expiro probe confirms it catches apps/api/node_modules and
    apps/web/node_modules, which a bare pattern would not.

What I should have done when filing this: built the two repositories
I had not built, instead of generalising one failure across a survey that
only established which repositories lacked the file. The survey was
sound; the diagnosis attached to it was not.

Pull requests: ahmad/expiro#87, Bayan/stem#16, ahmad/launchpad#88.

**Correcting this issue's central claim before it misleads anyone: only one of the three actually fails.** The body says `docker build` fails from a working copy that has run an install, and generalises that to all three repositories. I wrote that from one observation on `ahmad/expiro` and a survey of which repositories lack the file — not from building the other two. Built now, with an install already run in each: | Repository | Copies | `docker build` | Context before → after | |---|---|---|---| | `ahmad/expiro` | `COPY . .` | **fails** — `ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY` | 170.1 MB → 580 kB | | `Bayan/stem` | `COPY . .` after `npm ci` | succeeds | 152.9 MB → 290 kB | | `ahmad/launchpad` | **named paths**, not `COPY . .` | succeeds | 171.7 MB → 250 kB | **Why they differ, since it is the useful part:** - `ahmad/launchpad` copies `package.json`, the lockfiles, the configs and then `public`, `scripts`, `src` by name. The host's `node_modules` cannot reach an image layer, so nothing can break. It still uploads the whole context, because docker sends the context to the daemon regardless of which paths the Dockerfile later copies — which is the only cost there, and it is 171.7 MB of it. - `Bayan/stem` does end with `COPY . .`, but after `npm ci`, and **npm has no equivalent of pnpm's abort**. So the host's modules quietly overwrite the installed ones and the build carries on. I looked for the consequence — platform-specific `@esbuild/*` or `@rollup/rollup-*` packages differing between host and image — and found neither present in this dependency tree, so there is nothing to demonstrate. The overwrite is real; its effect here is latent. - Only `ahmad/expiro` combines `COPY . .` with **pnpm**, which refuses to proceed against a modules directory it did not create. That is the failure, and it is one repository, not three. **The acceptance criteria were written for the wrong premise**, so they are answered as follows rather than ticked uniformly: - "when `docker build` runs, then it completes" — now true everywhere; it was already true in two of the three. - "the reduction is recorded on the pull request" — done, per the table above, each measured on its own repository. - "the image's `node_modules` are the ones the build installed" — proven for `expiro` by pnpm completing at all, and for the other two by a `COPY . /ctx` probe image showing no `node_modules` reaches the context. - "every pattern that can nest is `**/`-prefixed" — yes in all three, and the `expiro` probe confirms it catches `apps/api/node_modules` and `apps/web/node_modules`, which a bare pattern would not. **What I should have done when filing this**: built the two repositories I had not built, instead of generalising one failure across a survey that only established which repositories lacked the file. The survey was sound; the diagnosis attached to it was not. Pull requests: ahmad/expiro#87, Bayan/stem#16, ahmad/launchpad#88.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
amtronics/platform-actions#75
No description provided.