Initializing Enclave...

How to Fix Docker Build Context Too Large & 'context canceled' Error (node_modules Not Ignored)

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–15 mins

TL;DR

  • What broke: Docker is shipping your entire project directory — including node_modules, .git, and build artifacts — to the daemon as build context. On large projects this exceeds hundreds of MB, causing the daemon to cancel the stream mid-transfer.
  • How to fix it: Create or correct your .dockerignore to exclude node_modules, .git, dist, and any other non-essential paths before the daemon even sees them.
  • Use the Client-Side Sandbox above to drop in your Dockerfile + .dockerignore and auto-generate the corrected ignore rules and optimized layer ordering.

The Incident (What Does the Error Mean?)

You ran docker build and got something like:

Sending build context to Docker daemon  1.4GB
Step 1/12 : FROM node:20-alpine
error: context canceled

or silently:

[+] Building 0.0s (0/0) DOCKER_BUILDKIT=1
error: context canceled

Immediate consequence: The build never starts. The Docker daemon accepted a multi-hundred-MB or multi-GB tar stream from the CLI client, hit a socket timeout or memory pressure threshold, and dropped the connection. Your CI pipeline is blocked. Every retry reproduces the same failure because the root cause — the missing .dockerignore — is still present.


The Attack Vector / Blast Radius

This is a build pipeline availability failure with a secondary secret exfiltration risk.

Performance blast radius:

  • node_modules in a typical React/Node project runs 300 MB–1.5 GB. Without .dockerignore, this is tarred and streamed to the daemon on every single build invocation, even if zero JS dependencies changed.
  • In CI (GitHub Actions, GitLab CI, Jenkins), this exhausts the runner's ephemeral disk or network buffer, causing cascading job failures across the queue.
  • BuildKit cache is completely defeated — layer caching cannot help you if the context invalidation happens at the COPY . . layer because node_modules timestamps change on every npm install.

Security blast radius:

  • .env files, .aws/credentials, private keys, and local secrets sitting in the project root get baked into the image layer if COPY . . executes successfully. They are then pushed to your registry and readable by anyone with docker history <image>.
  • .git directory inclusion leaks your entire commit history — including any secrets ever committed — into the image.

How to Fix It (The Solution)

Basic Fix — Create .dockerignore

Place this file at the same level as your Dockerfile:

- # (no .dockerignore exists)
+ node_modules
+ npm-debug.log
+ .git
+ .gitignore
+ .env
+ .env.*
+ dist
+ build
+ coverage
+ .nyc_output
+ .DS_Store
+ *.log
+ .dockerignore
+ README.md

Verify context size immediately:

# Dry-run: measure what would be sent
docker build --no-cache . 2>&1 | head -1
# Target: "Sending build context to Docker daemon  < 10MB"

Enterprise Best Practice — Fix the Dockerfile Layer Order Too

A correct .dockerignore alone isn't enough if your Dockerfile copies everything before installing dependencies. This destroys layer cache on every source change.

  FROM node:20-alpine AS deps
  WORKDIR /app

- COPY . .
- RUN npm ci

+ # Copy dependency manifests FIRST — these layers cache until package.json changes
+ COPY package.json package-lock.json ./
+ RUN npm ci --omit=dev
+
+ # Copy application source AFTER deps are installed
+ COPY src ./src
+ COPY public ./public

  FROM node:20-alpine AS runner
  WORKDIR /app
- COPY --from=deps /app .
+ COPY --from=deps /app/node_modules ./node_modules
+ COPY --from=deps /app/src ./src
+ COPY --from=deps /app/public ./public
+ COPY package.json ./

  EXPOSE 3000
  CMD ["node", "src/index.js"]

Why this matters: With correct layer ordering, npm ci only re-runs when package.json or package-lock.json changes. Source-only changes skip straight to the COPY src layer. Build times drop from minutes to seconds.


💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Enforce .dockerignore existence in pre-commit:

# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: check-dockerignore
      name: Require .dockerignore
      entry: bash -c 'test -f .dockerignore || (echo "ERROR: .dockerignore missing" && exit 1)'
      language: system
      pass_filenames: false

2. Gate on context size in CI (GitHub Actions):

- name: Check Docker build context size
  run: |
    SIZE=$(tar -czh --exclude-from=.dockerignore . | wc -c)
    echo "Build context: $((SIZE / 1024 / 1024)) MB"
    if [ $SIZE -gt 52428800 ]; then
      echo "ERROR: Build context exceeds 50MB. Check .dockerignore."
      exit 1
    fi

3. Hadolint in CI to catch COPY . . anti-patterns:

- name: Lint Dockerfile
  uses: hadolint/[email protected]
  with:
    dockerfile: Dockerfile
    failure-threshold: warning

Hadolint rule DL3045 warns when COPY . . is used without a preceding dependency-only copy stage.

4. Checkov policy to block secret-containing files from context:

checkov -d . --check CKV_DOCKER_2,CKV_DOCKER_3

This checks that sensitive file patterns are present in .dockerignore before the image is built in your pipeline.

Related Diagnostics

"Part of the Performance Utility Matrix."

View all 219 Performance Tools →