Context
Fourth friction signal from the first-real-world cloud-deployment exercise (siblings: #12014 #12015 #12016). Predecessor #10964 named this gap explicitly under "Docker context hygiene" and listed it as an AC, but the .dockerignore piece never shipped while other ACs from that ticket did land (Node 24 baseline, config bootstrap via initServerConfigs.mjs). Closing #10964 left this AC unfulfilled.
The symptom in the field: docker compose -f ai/deploy/docker-compose.yml build from a working neo checkout sends 20+ GB to the Docker daemon before crashing on disk-full inside the VM. The build cannot complete on any operator workstation that runs the actual Agent OS swarm against the same checkout.
The Problem
The canonical compose ai/deploy/docker-compose.yml:39-41 sets the build context to the repo root:
build:
context: ../..
dockerfile: ai/deploy/Dockerfile
Docker uses the .dockerignore at the build-context root, not next to the Dockerfile. The existing ai/deploy/.dockerignore is therefore never consulted for image builds via this compose stack:
node_modules
.neo-ai-data
dist
docs/output
.git
.github
*.log
V-B-A on a live neo checkout (dev HEAD, on a maintainer machine that runs the Agent OS swarm):
du -sh /path/to/neo .git node_modules dist .claude .neo-ai-data
90G (total)
18G .git
888M node_modules
622M dist
21G .claude
48G .neo-ai-data
Without a root .dockerignore, the legacy Docker builder (and even BuildKit's COPY . . step) sends/copies the entire 90 GB tree into the build context and the image layer. Concrete failure surfaced in the field: legacy build crashed after sending 20+ GB context with Error response from daemon: write /.git/objects/...: no space left on device against a default-sized Colima VM (100 GB disk).
The Architectural Reality
Affected:
- Missing:
/.dockerignore (build-context root). The single fix surface.
- Drift: ai/deploy/.dockerignore is unreachable under the current compose context configuration. Either delete it (single-source-of-truth in the root) or document it as a fallback for direct
docker build -f ai/deploy/Dockerfile ai/deploy/ invocations.
- Excluded from existing siblings:
.claude/ (21 GB of Claude Code worktrees + agent state) is not in ai/deploy/.dockerignore and would still leak even if that file were correctly placed.
The Dockerfile contract that depends on this:
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm rebuild better-sqlite3 && node ./ai/scripts/setup/initServerConfigs.mjs
The Fix
Add /.dockerignore at the repo root with at minimum:
<h1 class="neo-h1" data-record-id="6">Runtime / agent state — host-only, never deployed</h1>
.neo-ai-data
.claude
<h1 class="neo-h1" data-record-id="7">Build / test artifacts — rebuilt in-container</h1>
node_modules
dist
docs/output
coverage
<h1 class="neo-h1" data-record-id="8">Git substrate — image never needs git history</h1>
.git
.github
<h1 class="neo-h1" data-record-id="9">Logs + macOS noise</h1>
*.log
.DS_Store
The deploy image only consumes tracked source (ai/, buildScripts/, learn/, src/, test/ selectively, etc.) plus package*.json. A whitelist-shaped .dockerignore (exclude everything, then !include) is an option for stricter hygiene, but the blacklist above matches the existing ai/deploy/.dockerignore shape extended with .claude/.
Sibling cleanup decisions (optional, can be in same PR or follow-up):
- Delete
ai/deploy/.dockerignore and replace with a one-line readme/comment in the deploy dir pointing at the canonical root file.
- OR keep both and add a note in
ai/deploy/.dockerignore clarifying it is only consulted when builds run with ai/deploy/ as the context root (rare).
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
/.dockerignore (new file) |
Docker build-context semantics; ai/deploy/docker-compose.yml:39-41 build-context contract; #10964 unfulfilled AC |
Exclude .git, .claude, .neo-ai-data, node_modules, dist, docs/output, coverage, .github, logs, OS metadata |
None — image build cannot succeed against a working maintainer checkout without this |
Sibling ai/deploy/.dockerignore listing |
du -sh evidence: neo dir 90 GB, current build context sends entire tree |
ai/deploy/.dockerignore |
Same |
Either retire (single-source-of-truth in root) or annotate as fallback for direct docker build -f ai/deploy/Dockerfile ai/deploy/ callers |
Keep as-is, root .dockerignore takes precedence under compose |
Inline comment |
File present, currently unused by compose-driven builds |
Decision Record impact
aligned-with ADR 0014 — completes deployment substrate hygiene under existing cloud-topology authority. No ADR successor risk.
Acceptance Criteria
Out of Scope
- BuildKit / buildx adoption (orthogonal — BuildKit improves caching + parallelism but still needs
.dockerignore to avoid the COPY . . bloat).
- Image-size optimization beyond the
.dockerignore win (multi-stage tuning, base-image slimming, etc.).
- Per-service Dockerfile context narrowing (e.g., narrowing the kb-server context to only
ai/mcp/server/knowledge-base/ + shared deps) — viable future optimization but out of scope for this fix.
- Tracked-source whitelist (
!-rule shape) instead of blacklist — defer to a follow-up if blacklist proves brittle.
Avoided Traps
- Filing as
enhancement — rejected; this is a correctness bug. Cloud-deployment build is unbuildable from any neo checkout that actually runs the swarm (the only kind of checkout that has the failure-inducing runtime artifacts present).
- Re-opening #10964 — rejected per skill §11 (authorship respect; original closed by another author). Filing as targeted follow-up is the correct disposition.
- Whitelist-shaped
.dockerignore (exclude-all-then-allow) — rejected for the first cut; matches ai/deploy/.dockerignore's blacklist shape for consistency. Whitelist can be a follow-up if blacklist drift becomes a maintenance problem.
- Moving the Dockerfile out of
ai/deploy/ to change the canonical context — rejected; ADR 0014 owns the topology placement.
Related
- #10964 — predecessor (closed); named the .dockerignore gap as an AC, other ACs landed but this one did not.
- #12014 — sibling first-real-world-deployment friction (orchestrator co-location follow-up).
- #12015 — sibling Day-0 macOS prerequisites friction.
- #12016 — sibling compose interpolated cpus/memory quoting.
- ADR 0014 — cloud-deployment topology (substrate authority for the deploy substrate).
Handoff Retrieval Hints
- Grep:
find . -maxdepth 2 -name '.dockerignore' — pre-fix returns only ai/deploy/.dockerignore; post-fix returns root + sibling (or root only if sibling is retired).
- Disk evidence:
du -sh .git .claude .neo-ai-data on any maintainer checkout demonstrates the 90 GB context problem.
- Semantic: ".dockerignore root build context inflated", "ai/deploy build no space left on device".
- File anchor:
ai/deploy/docker-compose.yml:39-41 (build-context contract).
Context
Fourth friction signal from the first-real-world cloud-deployment exercise (siblings: #12014 #12015 #12016). Predecessor #10964 named this gap explicitly under "Docker context hygiene" and listed it as an AC, but the
.dockerignorepiece never shipped while other ACs from that ticket did land (Node 24 baseline, config bootstrap viainitServerConfigs.mjs). Closing #10964 left this AC unfulfilled.The symptom in the field:
docker compose -f ai/deploy/docker-compose.yml buildfrom a working neo checkout sends 20+ GB to the Docker daemon before crashing on disk-full inside the VM. The build cannot complete on any operator workstation that runs the actual Agent OS swarm against the same checkout.The Problem
The canonical compose ai/deploy/docker-compose.yml:39-41 sets the build context to the repo root:
build: context: ../.. dockerfile: ai/deploy/DockerfileDocker uses the
.dockerignoreat the build-context root, not next to the Dockerfile. The existing ai/deploy/.dockerignore is therefore never consulted for image builds via this compose stack:V-B-A on a live neo checkout (dev HEAD, on a maintainer machine that runs the Agent OS swarm):
Without a root
.dockerignore, the legacy Docker builder (and even BuildKit'sCOPY . .step) sends/copies the entire 90 GB tree into the build context and the image layer. Concrete failure surfaced in the field: legacy build crashed after sending 20+ GB context withError response from daemon: write /.git/objects/...: no space left on deviceagainst a default-sized Colima VM (100 GB disk).The Architectural Reality
Affected:
/.dockerignore(build-context root). The single fix surface.docker build -f ai/deploy/Dockerfile ai/deploy/invocations..claude/(21 GB of Claude Code worktrees + agent state) is not inai/deploy/.dockerignoreand would still leak even if that file were correctly placed.The Dockerfile contract that depends on this:
COPY package*.json ./ RUN npm ci --ignore-scripts COPY . . # ← 90 GB without .dockerignore RUN npm rebuild better-sqlite3 && node ./ai/scripts/setup/initServerConfigs.mjsThe Fix
Add
/.dockerignoreat the repo root with at minimum:The deploy image only consumes tracked source (
ai/,buildScripts/,learn/,src/,test/selectively, etc.) pluspackage*.json. A whitelist-shaped.dockerignore(exclude everything, then!include) is an option for stricter hygiene, but the blacklist above matches the existingai/deploy/.dockerignoreshape extended with.claude/.Sibling cleanup decisions (optional, can be in same PR or follow-up):
ai/deploy/.dockerignoreand replace with a one-line readme/comment in the deploy dir pointing at the canonical root file.ai/deploy/.dockerignoreclarifying it is only consulted when builds run withai/deploy/as the context root (rare).Contract Ledger
/.dockerignore(new file)ai/deploy/docker-compose.yml:39-41build-context contract; #10964 unfulfilled AC.git,.claude,.neo-ai-data,node_modules,dist,docs/output,coverage,.github, logs, OS metadataai/deploy/.dockerignorelistingdu -shevidence: neo dir 90 GB, current build context sends entire treeai/deploy/.dockerignoredocker build -f ai/deploy/Dockerfile ai/deploy/callers.dockerignoretakes precedence under composeDecision Record impact
aligned-with ADR 0014— completes deployment substrate hygiene under existing cloud-topology authority. No ADR successor risk.Acceptance Criteria
/.dockerignoreexists at the neo repo root..git,.claude,.neo-ai-data,node_modules,dist,docs/output,coverage,*.log,.DS_Store.docker compose -f ai/deploy/docker-compose.yml --profile cloud --profile local-model buildsucceeds from a working maintainer checkout that runs the local Agent OS swarm (regression guard against the disk-full crash this ticket targets).docker compose build(or equivalent) is < 100 MB after the fix.ai/deploy/.dockerignoreis decided: retired with replacement comment, OR retained with explicit "fallback only" annotation.Out of Scope
.dockerignoreto avoid theCOPY . .bloat)..dockerignorewin (multi-stage tuning, base-image slimming, etc.).ai/mcp/server/knowledge-base/+ shared deps) — viable future optimization but out of scope for this fix.!-rule shape) instead of blacklist — defer to a follow-up if blacklist proves brittle.Avoided Traps
enhancement— rejected; this is a correctness bug. Cloud-deployment build is unbuildable from any neo checkout that actually runs the swarm (the only kind of checkout that has the failure-inducing runtime artifacts present)..dockerignore(exclude-all-then-allow) — rejected for the first cut; matchesai/deploy/.dockerignore's blacklist shape for consistency. Whitelist can be a follow-up if blacklist drift becomes a maintenance problem.ai/deploy/to change the canonical context — rejected; ADR 0014 owns the topology placement.Related
Handoff Retrieval Hints
find . -maxdepth 2 -name '.dockerignore'— pre-fix returns onlyai/deploy/.dockerignore; post-fix returns root + sibling (or root only if sibling is retired).du -sh .git .claude .neo-ai-dataon any maintainer checkout demonstrates the 90 GB context problem.ai/deploy/docker-compose.yml:39-41(build-context contract).