Skip to content

Fix: Docker build sending large build context / slow Docker build

FixDevs ·

Quick Answer

How to fix Docker build sending large build context caused by missing .dockerignore, node_modules in context, large files, and inefficient Dockerfile layers.

The Error

You run docker build and see:

Sending build context to Docker daemon  2.5GB

The build takes forever, uses excessive disk space, or fails with:

Error response from daemon: Error processing tar file: exit status 1
COPY failed: file not found in build context or excluded by .dockerignore
no space left on device

Docker sends the entire build context (the directory you specify) to the Docker daemon before starting the build. If that directory contains large files, node_modules, .git, or data files, the build context is unnecessarily huge.

Why This Happens

When you run docker build ., Docker tars up the entire current directory and sends it to the daemon. This is the “build context.” Every file in that directory is included unless excluded by .dockerignore.

Common causes:

  • Missing .dockerignore file. Everything is included by default.
  • node_modules/ in the context. Can be hundreds of megabytes.
  • .git/ directory. Repository history can be very large.
  • Data files, logs, or databases. SQLite databases, CSV files, logs.
  • Build artifacts. dist/, build/, target/, __pycache__/.
  • Large binary files. Videos, images, trained ML models.
  • Wrong build context path. Running docker build / instead of docker build ..

Fix 1: Create a .dockerignore File

The most important fix. Create .dockerignore in your project root:

# Dependencies
node_modules
.npm
bower_components
vendor

# Version control
.git
.gitignore
.svn

# Build artifacts
dist
build
target
__pycache__
*.pyc
*.pyo

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

# OS files
.DS_Store
Thumbs.db

# Docker files (not needed in the image)
Dockerfile
docker-compose.yml
docker-compose*.yml
.dockerignore

# Documentation
README.md
CHANGELOG.md
LICENSE
docs

# Test files
coverage
.nyc_output
*.test.js
*.spec.js
__tests__

# Environment and secrets
.env
.env.*
*.pem
*.key

# Logs
*.log
logs

# Data files
*.sql
*.sqlite
*.csv
data

Verify what is included in the context:

# See what Docker would include (without actually building)
# Create a tar and check its size
tar -cf - --exclude='.git' . | wc -c

# Or use docker build with progress
docker build --progress=plain .

Pro Tip: A good .dockerignore is as important as a good .gitignore. Start with everything excluded and only include what the build actually needs. This can reduce build context from gigabytes to megabytes and speed up builds dramatically.

Fix 2: Use Multi-Stage Builds

Multi-stage builds keep the final image small:

Before — everything in one stage:

FROM node:22
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
# Final image includes source code, node_modules, AND build output

After — multi-stage:

# Build stage
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

For Go applications:

FROM golang:1.23 AS build
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /server

FROM scratch
COPY --from=build /server /server
ENTRYPOINT ["/server"]

The final image only contains the compiled binary — no source code, no build tools.

Fix 3: Optimize COPY Order for Layer Caching

Docker caches layers. If a layer has not changed, Docker reuses the cache. Order your COPY instructions to maximize cache hits:

Bad — COPY everything first (cache invalidated on any file change):

COPY . .
RUN npm ci
RUN npm run build

Good — copy dependency files first, then source:

# Layer 1: Copy only dependency files (rarely change)
COPY package.json package-lock.json ./
RUN npm ci

# Layer 2: Copy source (changes frequently)
COPY . .
RUN npm run build

Now npm ci is cached unless package.json or package-lock.json changes. Source code changes only invalidate the last two layers.

For Python:

COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .

For Go:

COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /app

Common Mistake: Putting COPY . . before RUN npm ci. This means any source code change invalidates the npm install cache, forcing a full reinstall every build. Always copy dependency manifests separately.

Fix 4: Use Specific COPY Paths

Instead of COPY . ., copy only what you need:

# Instead of COPY . .
COPY src/ ./src/
COPY public/ ./public/
COPY package.json package-lock.json tsconfig.json ./

This reduces the context that matters and makes the build more predictable.

For large monorepos:

# Only copy the relevant workspace
COPY packages/my-service/package.json ./
COPY packages/my-service/src/ ./src/
COPY packages/shared/src/ ./shared/src/

Fix 5: Use BuildKit for Faster Builds

Docker BuildKit is faster and more efficient:

# Enable BuildKit
DOCKER_BUILDKIT=1 docker build .

# Or set it permanently
export DOCKER_BUILDKIT=1

# In Docker Desktop, BuildKit is enabled by default

BuildKit advantages:

  • Parallel layer building
  • Better caching
  • Secret mounts (no secrets in layers)
  • SSH forwarding

Mount secrets without storing in the image:

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
docker build --secret id=npm_token,src=.npmrc .

Fix 6: Check Build Context Size

Measure your build context:

# Quick check — how big is the directory?
du -sh --exclude=.git .

# What's taking the most space?
du -h --max-depth=1 . | sort -rh | head -20

# Build with verbose output to see context size
docker build --progress=plain . 2>&1 | head -5
# Sending build context to Docker daemon  15.2MB

Target: Under 50MB. If your context is over 100MB, you almost certainly need a better .dockerignore.

Fix 7: Use Remote Build Context

Build from a Git URL instead of a local directory:

# Build from a Git repository
docker build https://github.com/user/repo.git#main

# Build from a specific directory in the repo
docker build https://github.com/user/repo.git#main:docker/

# Build from a tarball URL
docker build https://example.com/context.tar.gz

Use stdin for the Dockerfile:

docker build -f - . << 'EOF'
FROM alpine
RUN echo "hello"
EOF

Fix 8: Use .dockerignore Patterns Effectively

.dockerignore supports the same pattern syntax as .gitignore:

# Exclude everything
*

# Include only what's needed
!src/
!package.json
!package-lock.json
!tsconfig.json
!.env.example

# Exclude within included directories
src/**/*.test.ts
src/**/*.spec.ts

The “exclude everything, include selectively” pattern is the most effective approach for large projects. It guarantees only the minimum files are in the context.

Test your .dockerignore:

# List files that would be included in the build context
# (No built-in Docker command, but you can simulate it)
rsync -avn --exclude-from=.dockerignore . /dev/null

Still Not Working?

Check for symlinks. Symlinked directories can include unexpected large directories in the context.

Check for Docker disk space:

docker system df
# Shows space used by images, containers, volumes, and cache

docker system prune
# Removes unused data

Use --no-cache for debugging:

docker build --no-cache .

Consider using Kaniko, Buildpacks, or Nix for alternative build strategies that do not require a build context.

For Docker permission issues, see Fix: Docker permission denied socket. For Docker entrypoint errors, see Fix: Docker exec /entrypoint.sh: no such file or directory. For Docker container name conflicts, see Fix: Docker container name already in use.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles