Understanding Docker Build Context: Why Your 100MB Project Becomes 3.5GB

Docker's build context includes everything in your directory - and I mean EVERYTHING. Your source code, sure, but also that 300MB .git folder, the node_modules directory that's bigger than Windows 95, and every random file you've accumulated over months of development. Docker's "upload everything" mentality is about as helpful as a screen door on a submarine.

What Actually Gets Sent

Docker's build context includes everything in the build directory by default:

  • Your source code (good)
  • Git history in .git/ (usually unnecessary - can be 100MB+)
  • Node.js dependencies in node_modules/ (often 200MB+)
  • Build artifacts, logs, temporary files
  • IDE files, OS files like .DS_Store
  • Previous Docker layers or images saved locally

Docker Logo

Docker sends the entire build context to the daemon before it even looks at your Dockerfile. So that 1GB of crap gets uploaded even if your Dockerfile only copies a single 5MB file. Yeah, it's as stupid as it sounds.

The Real-World Impact

I've seen this nightmare personally: that classic Stack Overflow case where someone's 1GB RPM file turned into a 3.5GB context transfer because Docker uploaded their entire project history. I once had a build context that included our entire database backup because some genius put it in the project root. That took down prod for 2 hours while we waited for a 4GB context to upload over our shitty corporate VPN that maxed out at 10Mbps on a good day.

Real cost of massive contexts (learned this the hard way):

  • Build times that kill productivity (10 minutes for a 3GB context, developers just give up and go get coffee)
  • AWS charges for compute time even when builds fail (our monthly bill doubled because of these massive context transfers)
  • Memory usage spikes crash Docker daemon (OOMKilled on 8GB instances, had to upgrade to 16GB)
  • CI/CD pipelines become unreliable as fuck (GitHub Actions failed 60% of the time, team lost confidence in deployments)

Why This Happens More Often Now

Modern development makes this worse:

  • Monorepos: Everything in one massive repo
  • Microservices: Dockerfile at root pulling the entire universe
  • Generated files: Webpack vomits build artifacts everywhere
  • Development tools: Test coverage and reports pile up
  • Docker-in-Docker: Because we love making things complicated

Docker's "send everything just in case" approach is like packing your entire house when you're going camping for the weekend. Unlike Git which tracks specific files, Docker assumes you might need that random 2-year-old log file buried in your project folder.

The BuildKit vs Legacy Builder Difference

BuildKit is a lifesaver if you're on Docker 18.09+. But good fucking luck if you're stuck on corporate Docker 17.x that your company refuses to upgrade for "security reasons." When BuildKit works (and ops lets you enable it), my builds went from taking forever to finishing before I can even alt-tab to check Slack. We're talking actual 51 seconds down to 0.166 seconds with identical Dockerfiles.

Legacy Docker Build:
$ docker build .
Sending build context to Docker daemon  4.315GB
[... 51 seconds later ...]
Docker BuildKit:
$ DOCKER_BUILDKIT=1 docker build .
[+] Building 0.1s (5/5) FINISHED
[... completes in 0.166 seconds ...]

BuildKit only transfers files actually referenced in the Dockerfile instead of uploading your entire project like some kind of digital hoarder. Finally, some sanity in the Docker build process.

Docker BuildKit Architecture

Diagnosing Your Context Size Problem

Check what's being included:
## See files that would be sent (requires ripgrep)
rg -uuu --ignore-file .dockerignore --files . | head -20

## Or with find (slower but available everywhere)
find . -name .dockerignore -exec cat {} \; | grep -v "^#"

ripgrep respects ignore files and is stupidly fast for this kind of debugging.

Monitor context preparation:
## Time context sending specifically  
time docker build --no-cache -t test .

The first line of output shows context size. If it's unexpectedly large, you have files being included that shouldn't be.

Common size offenders:
  • .git/ directory (often 50MB-2GB)
  • node_modules/ (typically 100MB-500MB)
  • Build outputs (dist/, target/, build/)
  • IDE files (.vscode/, .idea/)
  • OS files (.DS_Store, Thumbs.db)
  • Log files, temporary files, cache directories

The fix isn't moving files around - it's telling Docker to ignore the shit you don't need using .dockerignore.

Alright, enough bitching about Docker's stupidity. Let's fix this mess with solutions that actually work in the real world.

Practical Solutions: From 3.5GB to 50MB Build Context

Here's how to fix this nightmare, ranked by how much of your weekend you're willing to sacrifice and how much corporate red tape you'll have to cut through. I've tried all of these after spending way too many hours watching Docker upload gigabytes of garbage while deployment deadlines whooshed by.

Solution 1: Create a Comprehensive .dockerignore File

The .dockerignore file is your first line of defense against Docker's "upload everything" stupidity. It works like .gitignore except Docker actually respects it (unlike how Git still tracks files you thought you ignored). This should be the first thing you create after your Dockerfile, and it'll save you hours of watching progress bars that never seem to end.

Essential .dockerignore template that actually works:

## Version control
.git
.gitignore

## Dependencies  
node_modules/
bower_components/
vendor/

## Build outputs
build/
dist/
target/
*.min.js
*.min.css

## IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~

## OS generated files
.DS_Store
.DS_Store?
Thumbs.db
ehthumbs.db

## Logs and databases
*.log
*.sql
*.sqlite

## Runtime data
pids/
logs/
*.pid
*.seed

## Coverage directory used by tools like istanbul
coverage/

## Documentation
docs/
*.md
README*

## Test files (usually not needed in production images)
test/
tests/
__tests__/
spec/
*.test.js
*.spec.js

## Development configuration
.env.local
.env.development
docker-compose.yml
docker-compose.*.yml

## Temporary files
tmp/
temp/
*.tmp
*.temp

Project-specific additions:

For Node.js projects:

npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.yarn

For Python projects:

__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.venv/
.pytest_cache/
.tox/

For Java projects:

*.class
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
target/
.m2/

For Go projects:

## Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

## Test binary
*.test

## Output of the go coverage tool
*.out

## Dependency directories
vendor/

A decent .dockerignore cuts build context by 80-95% in most web apps. I've seen it drop 3GB contexts down to 50MB.

Solution 2: Enable Docker BuildKit

Docker BuildKit

Docker BuildKit fundamentally changes how context is handled. Enable it globally or per-build:

Global enablement:

## Add to ~/.bashrc or ~/.zshrc
export DOCKER_BUILDKIT=1

Per-build enablement:

DOCKER_BUILDKIT=1 docker build -t myapp .

With docker-compose:

## In docker-compose.yml
version: '3.8'
services:
  app:
    build:
      context: .
      target: production
    environment:
      - DOCKER_BUILDKIT=1

BuildKit improvements (when it doesn't break):

  • Only sends files referenced in Dockerfile (finally, some sanity)
  • Parallel layer processing (unless you're on Windows containers - then it's fucked)
  • Better caching strategies (works great until Docker 20.10.8 where they broke cache invalidation)
  • More efficient context transfer (saved my ass on slow hotel WiFi during that Amsterdam conference deployment)

I've seen builds go from taking forever (like 45+ seconds) to finishing almost instantly with BuildKit enabled. Same Dockerfile, same context, completely different experience.

Solution 3: Optimize Directory Structure

Directory Structure

Move Dockerfile to a subdirectory:

project/
├── docker/
│   ├── Dockerfile
│   └── app/
│       ├── package.json
│       └── src/
├── docs/
├── tests/
└── other-large-directories/

Build from the subdirectory:

cd docker
docker build -t myapp .

Or use specific context with -f flag:

docker build -f docker/Dockerfile docker/

This physically limits what can be sent to Docker daemon.

Solution 4: Use Remote Context (If Security Will Let You)

For large projects, build on remote Docker instances:

## Build on remote Docker host via SSH (assuming security allows SSH keys)
docker -H ssh://user@remote-host build -t myapp .

## Or with buildx for advanced features (good luck getting this approved)
docker buildx build --builder remote-builder -t myapp .

Remote builds only transfer necessary files, but getting ops approval for remote Docker hosts is like getting approval for a kidney transplant. I spent 3 weeks filling out security assessments just to set up a build server in the same VPC.

Solution 5: Multi-stage Builds with Careful Context

Multi-stage Builds

Structure Dockerfiles to minimize context usage:

## Stage 1: Build dependencies (minimal context needed)
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

## Stage 2: Build application (only copy source files)
FROM node:18-alpine AS builder
WORKDIR /app  
COPY package*.json ./
RUN npm ci
COPY src/ src/
RUN npm run build

## Stage 3: Production (copy only built artifacts)
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./
CMD ["npm", "start"]

This copies only what's needed at each stage. No more uploading your entire project when you only need the final build artifact.

Solution 6: Build Context Debugging

Debug Search

When .dockerignore isn't enough, debug what's still being included:

Using ripgrep (recommended):

## See what files would be sent to Docker
rg -uuu --ignore-file .dockerignore --files . | sort

## Find largest files in context
rg -uuu --ignore-file .dockerignore --files . | xargs ls -lh | sort -k5 -hr | head -10

Using find (available everywhere):

## List all files that would be sent
find . -type f | grep -v -f .dockerignore

## Show context size by directory
du -sh * | sort -hr

Check context preparation time:

time docker build --no-cache -t test . 2>&1 | head -5

Advanced: Context Optimization Patterns

For monorepos, create service-specific contexts:

monorepo/
├── services/
│   ├── api/
│   │   ├── Dockerfile
│   │   ├── .dockerignore  # Service-specific ignores
│   │   └── src/
│   └── web/
│       ├── Dockerfile
│       ├── .dockerignore
│       └── src/
└── shared/

For CI/CD, prepare minimal context (if your CI runners have enough disk space):

## In CI, create clean context directory  
mkdir -p ./docker-context
cp -r src/ package.json Dockerfile ./docker-context/
docker build -t myapp ./docker-context/

Pro tip: Test this locally first. I once deployed a CI script that created context directories but forgot to clean them up. Filled the CI runner's 20GB disk in a week, took down all builds until someone manually SSHed in to delete 400GB of forgotten docker-context directories.

For development vs production, use different contexts:

## Development (includes test files, dev tools)
docker build -f Dockerfile.dev .

## Production (minimal context)
docker build -f Dockerfile.prod ./production-context/

Build context should only include what Docker actually needs, not every random file in your project. Most projects can cut context size by 90% without breaking anything.

So which approach should you use? Depends on how much of your weekend you're willing to sacrifice and whether your ops team will let you enable BuildKit.

Build Context Optimization Methods Comparison

Method

Effectiveness

Implementation Effort

Best For

Limitations

Create .dockerignore

80-95% reduction

Low (1 hour setup)

All projects

Must maintain ignore rules

Enable BuildKit

70-90% faster builds

Very Low (1 command)

All Docker 18.09+ users

Limited Windows container support

Reorganize directory structure

90-99% reduction

Medium (refactoring)

New projects, monorepos

Breaks existing build processes

Remote context/SSH builds

Network transfer only

Medium (infra setup)

Large teams, CI/CD

Requires remote Docker access

Multi-stage builds

50-80% reduction

Medium (Dockerfile rewrite)

Complex applications

More complex Dockerfile

Separate build context

95-99% reduction

High (process changes)

Critical performance cases

Manual context preparation

Docker Build Context FAQ: Common Problems and Solutions

Q

Why does my .dockerignore file not work? (AKA: Why is Docker still uploading every damn file?)

A

Common causes:

  • .dockerignore must be in the same directory as your build context (where you run docker build)
  • Patterns don't match exactly - use ** for subdirectories: **/node_modules/
  • Comments must start with # at line beginning
  • Windows path separator issues - use forward slashes: build/output/ not build\output\

Test your .dockerignore:

## See what files are still included
rg -uuu --ignore-file .dockerignore --files . | head -20
Q

My build context is still 2GB after adding .dockerignore - what's wrong?

A

Check these common oversights:

  • .git directory not excluded (can be 500MB-2GB)
  • Build outputs like dist/, target/, build/ still included
  • IDE files (.vscode/, .idea/) not ignored
  • Log files or temporary files accumulating

Find the largest contributors:

rg -uuu --ignore-file .dockerignore --files . | xargs ls -lh | sort -k5 -hr | head -10
Q

Does BuildKit actually reduce context size or just transfer speed?

A

BuildKit reduces both. Traditional Docker sends entire context then processes Dockerfile. BuildKit analyzes the Dockerfile first and only transfers files actually referenced by COPY or ADD commands.

However: Commands like COPY . . still transfer everything not excluded by .dockerignore, so you still need proper ignore files.

Q

Can I use symlinks to reduce context size?

A

No - Docker doesn't follow symlinks because "security reasons" (translation: it would make Docker builds actually logical). Symlinks just get ignored, breaking your build in mysterious ways.

What actually works:

  • Use bind mounts at runtime: docker run -v /host/path:/container/path
  • Copy files into context before building (manual but reliable)
  • Multi-context builds if you're on Docker 20.10+ and ops hasn't locked you to ancient versions
Q

Why do I get "COPY failed: no such file or directory" after adding .dockerignore?

A

Congratulations, you excluded something your Dockerfile actually needs and now your build is failing with the world's most unhelpful error message. Common mistakes:

  • Excluding package.json needed by COPY package*.json ./
  • Excluding source files needed by COPY src/ ./src/
  • Excluding configuration files referenced in Dockerfile

Fix: Review your Dockerfile's COPY and ADD commands, ensure required files aren't ignored.

Q

Should I put .dockerignore in .gitignore?

A

Usually no. The .dockerignore file is part of your Docker build configuration and should be versioned with your code. Other developers need the same ignore rules to get consistent builds.

Exception: If you have environment-specific ignore rules, create .dockerignore.local and add that to .gitignore.

Q

My Node.js project context is still 300MB after excluding node_modules - what else to check?

A

Common Node.js context bloat:

  • Package manager caches: .npm/, .yarn/, .pnpm-store/
  • Build outputs: dist/, build/, .next/, .nuxt/
  • Log files: npm-debug.log, yarn-error.log
  • Test coverage: coverage/, .nyc_output/
  • TypeScript compilation: *.tsbuildinfo

Add to .dockerignore:

npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.yarn
coverage/
.nyc_output/
*.tsbuildinfo
Q

How do I handle secrets in build context without including them?

A

Never put secrets in build context. Use Docker build secrets instead:

## syntax=docker/dockerfile:1
FROM alpine
RUN --mount=type=secret,id=mypassword cat /run/secrets/mypassword
echo "my-secret" | docker build --secret id=mypassword,src=- .

Secrets are mounted at build time but not stored in image layers or context.

Q

BuildKit doesn't work in my environment - alternatives?

A

Windows containers: BuildKit support on Windows is like trying to run Linux software on Windows 95 - technically possible but you'll want to kill yourself. Stick with aggressive .dockerignore files and directory restructuring.

Old Docker versions: Upgrade if possible (hint: it's not possible in most corporate environments). For Docker < 18.09 because your company is stuck in 2017:

  • Aggressive .dockerignore usage (your only real weapon)
  • Directory restructuring to minimize context (prepare for breaking everything)
  • Multi-stage builds to at least reduce final image size (doesn't help context but makes you feel better)

Corporate environments: Good luck getting approval for anything newer than whatever Docker version they installed 3 years ago. Security won't let you upgrade because "it might break things" and ops won't let you enable BuildKit because "it's not in the approved software list."

Q

My CI/CD build context transfer times out - what the fuck do I do?

A

Short-term fixes (band-aids that might work):

  • Increase CI/CD timeout limits (if your DevOps team will let you)
  • Use .dockerignore to reduce context size (should have done this already)
  • Build during lower-traffic periods (3am builds, living the dream)

Long-term solutions (requires actual work):

  • Set up remote Docker builders closer to CI/CD (costs money, management hates it)
  • Use container registry caching (works until cache mysteriously gets corrupted at 2am)
  • Split large applications into smaller services (6-month refactoring project nobody wants to fund)
  • Implement incremental build strategies (good luck explaining this to PM without their eyes glazing over)
Q

How do I debug "context canceled" or "failed to load LLB" errors?

A

These errors often indicate:

  • Context transfer interrupted due to size/timeout
  • Network issues during context transfer
  • Docker daemon running out of disk space

Debugging steps:

## Check available disk space
df -h

## Monitor context transfer
docker build --progress=plain .

## Try with smaller context
mkdir test-context && cp Dockerfile test-context/
docker build test-context/
Q

Should I commit my .dockerignore to version control?

A

Yes, absolutely. .dockerignore is build configuration that ensures consistent builds across environments. All team members should have the same ignore rules.

Best practices:

  • Version control .dockerignore with your code
  • Use project-specific ignore patterns
  • Document any unusual exclusions
  • Test ignore rules in CI/CD to catch issues early
Q

Can I have multiple .dockerignore files for different environments?

A

Not directly, but you can work around this:

Option 1: Environment-specific build contexts

## Production build with minimal context
docker build -f Dockerfile.prod ./prod-context/

## Development build with full context
docker build -f Dockerfile.dev .

Option 2: Copy ignore files before build

cp .dockerignore.prod .dockerignore
docker build -t myapp:prod .

cp .dockerignore.dev .dockerignore
docker build -t myapp:dev .
Q

My Docker build works locally but fails in CI with context errors - why?

A

Common CI-specific issues:

  • CI workspace includes extra files (test reports, cache directories, that weird artifact from last Tuesday's failed build)
  • Different Docker versions because ops won't let CI use the same version as dev (security theater)
  • CI checkout includes full Git history because someone enabled deep clone for "debugging purposes"
  • File permissions that work on your Mac but explode on Linux CI runners

Solutions that might actually work:

  • Use identical .dockerignore locally and in CI (test this first, don't assume it works)
  • Clean CI workspace before building (add rm -rf commands, feels wrong but works)
  • Use shallow Git clones: git clone --depth 1 (assuming your CI allows it)
  • Test builds on Linux VM that matches CI (VirtualBox is your friend)

Related Tools & Recommendations

integration
Similar content

Jenkins Docker Kubernetes CI/CD: Deploy Without Breaking Production

The Real Guide to CI/CD That Actually Works

Jenkins
/integration/jenkins-docker-kubernetes/enterprise-ci-cd-pipeline
100%
tool
Recommended

Google Kubernetes Engine (GKE) - Google's Managed Kubernetes (That Actually Works Most of the Time)

Google runs your Kubernetes clusters so you don't wake up to etcd corruption at 3am. Costs way more than DIY but beats losing your weekend to cluster disasters.

Google Kubernetes Engine (GKE)
/tool/google-kubernetes-engine/overview
69%
troubleshoot
Similar content

Fix Kubernetes Service Not Accessible: Stop 503 Errors

Your pods show "Running" but users get connection refused? Welcome to Kubernetes networking hell.

Kubernetes
/troubleshoot/kubernetes-service-not-accessible/service-connectivity-troubleshooting
69%
tool
Similar content

Podman: Rootless Containers, Docker Alternative & Key Differences

Runs containers without a daemon, perfect for security-conscious teams and CI/CD pipelines

Podman
/tool/podman/overview
54%
troubleshoot
Similar content

Docker Desktop CVE-2025-9074 Fix: Container Escape Mitigation Guide

Any container can take over your entire machine with one HTTP request

Docker Desktop
/troubleshoot/cve-2025-9074-docker-desktop-fix/container-escape-mitigation
42%
tool
Recommended

GitHub Actions Security Hardening - Prevent Supply Chain Attacks

integrates with GitHub Actions

GitHub Actions
/tool/github-actions/security-hardening
40%
alternatives
Recommended

Tired of GitHub Actions Eating Your Budget? Here's Where Teams Are Actually Going

integrates with GitHub Actions

GitHub Actions
/alternatives/github-actions/migration-ready-alternatives
40%
tool
Recommended

GitHub Actions - CI/CD That Actually Lives Inside GitHub

integrates with GitHub Actions

GitHub Actions
/tool/github-actions/overview
40%
troubleshoot
Similar content

Trivy Scanning Failures - Common Problems and Solutions

Fix timeout errors, memory crashes, and database download failures that break your security scans

Trivy
/troubleshoot/trivy-scanning-failures-fix/common-scanning-failures
37%
troubleshoot
Similar content

Docker 'No Space Left on Device' Error: Fast Fixes & Solutions

Stop Wasting Hours on Disk Space Hell

Docker
/troubleshoot/docker-no-space-left-on-device-fix/no-space-left-on-device-solutions
34%
tool
Recommended

containerd - The Container Runtime That Actually Just Works

The boring container runtime that Kubernetes uses instead of Docker (and you probably don't need to care about it)

containerd
/tool/containerd/overview
31%
tool
Recommended

MongoDB Atlas Enterprise Deployment Guide

built on MongoDB Atlas

MongoDB Atlas
/tool/mongodb-atlas/enterprise-deployment
29%
news
Recommended

Google Guy Says AI is Better Than You at Most Things Now

Jeff Dean makes bold claims about AI superiority, conveniently ignoring that his job depends on people believing this

OpenAI ChatGPT/GPT Models
/news/2025-09-01/google-ai-human-capabilities
29%
pricing
Recommended

Docker, Podman & Kubernetes Enterprise Pricing - What These Platforms Actually Cost (Hint: Your CFO Will Hate You)

Real costs, hidden fees, and why your CFO will hate you - Docker Business vs Red Hat Enterprise Linux vs managed Kubernetes services

Docker
/pricing/docker-podman-kubernetes-enterprise/enterprise-pricing-comparison
28%
tool
Similar content

Docker: Package Code, Run Anywhere - Fix 'Works on My Machine'

No more "works on my machine" excuses. Docker packages your app with everything it needs so it runs the same on your laptop, staging, and prod.

Docker Engine
/tool/docker/overview
28%
tool
Recommended

Jenkins - The CI/CD Server That Won't Die

integrates with Jenkins

Jenkins
/tool/jenkins/overview
27%
tool
Recommended

Jenkins Production Deployment - From Dev to Bulletproof

integrates with Jenkins

Jenkins
/tool/jenkins/production-deployment
27%
troubleshoot
Similar content

Fix Snyk Authentication Registry Errors: Deployment Nightmares Solved

When Snyk can't connect to your registry and everything goes to hell

Snyk
/troubleshoot/snyk-container-scan-errors/authentication-registry-errors
26%
troubleshoot
Similar content

Git Fatal Not a Git Repository: Enterprise Security Solutions

When Git Security Updates Cripple Enterprise Development Workflows

Git
/troubleshoot/git-fatal-not-a-git-repository/enterprise-security-scenarios
24%
troubleshoot
Similar content

Fix Trivy & ECR Container Scan Authentication Issues

Trivy says "unauthorized" but your Docker login works fine? ECR tokens died overnight? Here's how to fix the authentication bullshit that keeps breaking your sc

Trivy
/troubleshoot/container-security-scan-failed/registry-access-authentication-issues
22%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization