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
.dockerignoreto excludenode_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 +
.dockerignoreand 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_modulesin 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 becausenode_modulestimestamps change on everynpm install.
Security blast radius:
.envfiles,.aws/credentials, private keys, and local secrets sitting in the project root get baked into the image layer ifCOPY . .executes successfully. They are then pushed to your registry and readable by anyone withdocker history <image>..gitdirectory 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.