Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Copy Only Needed Files

Learn how to copy only needed files into Docker images to minimize image size and improve build performance. This lesson covers best practices for selective file copying using .dockerignore and efficient COPY instructions.

Focus: copy only needed files into images

Sponsored

Every Docker image you build is a promise: it contains everything your application needs to run — and nothing it doesn't. Unfortunately, it's surprisingly easy to accidentally include local virtual environments, .git folders, or node_modules that bloat your image by hundreds of megabytes. That extra baggage slows down builds, increases push/pull times, and wastes disk space. Learning to ⭐copy only needed files into images is the single most impactful habit you can adopt for lean, fast Docker builds.

The problem this lesson solves

Imagine you have a Python project with 200 MB of dependencies in venv/, a hidden .env file, and developer-only scripts inside tests/. When you run COPY . /app, everything lands in the image. The resulting image is - Ridiculously large – each rebuild sends the entire context to the Docker daemon. - Slow to build – even trivial file changes invalidate the COPY . layer cache, forcing full rebuilds. - Unsafe – secret .env files or SSH keys might leak into the image layer.

This lesson directly addresses the pain of bloated, unmanageable builds. By selectively copying only what your application needs at runtime, you produce smaller, faster, more secure images.

Core concept / mental model

Think of your Dockerfile as a packing list for a trip. You don't throw your entire wardrobe into a suitcase — you pick only the clothes you'll actually wear. Similarly, the COPY instruction is your suitcase: every file you add ends up in the final image.

Two strategies work together to achieve selective copying:

  • The .dockerignore file – a gatekeeper that tells Docker which files not to send to the build context. Think of it as a "do not pack" list.
  • Precise COPY paths – instead of COPY . /app, you write COPY src/ /app/src/ and COPY requirements.txt /app/. You explicitly list the files you need.

💡 Pro tip: A .dockerignore file is not optional for production Dockerfiles. It's a minimum requirement.

How it works step by step

  1. Create a .dockerignore file in your project root – same directory as your Dockerfile.
  2. List patterns for every file or directory that must not enter the build context: - node_modules (npm packages) - .venv or venv (Python virtual environment) - .git (version control history) - .env (environment variables — often contain secrets) - .log, __pycache__/, .pyc (generated artifacts)
  3. Use explicit COPY instructions that target only the directories and files your application needs: - COPY requirements.txt /app/ - COPY src/ /app/src/
  4. Build the image with docker build -t myapp . – the build context sent to the daemon is now significantly smaller.

Hands-on walkthrough

Let's do a concrete example. Create a project structure like this:

my-python-app/
├── Dockerfile
├── .dockerignore
├── requirements.txt
├── src/
│   └── app.py
├── tests/
│   └── test_app.py
├── .env
└── .venv/
    └── (hundreds of files)

Example 1: Without selective copying

# BAD – copies everything
FROM python:3.11-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "src/app.py"]

When you build:

docker build -t myapp-bloated .
# Sending build context to Docker daemon  2.1GB
# ...

Example 2: With .dockerignore and explicit COPY

# .dockerignore
.venv
.venv/**
.git
.git/**
.env
tests/
tests/**
__pycache__/
*.pyc
*.log
# GOOD – selective copy
FROM python:3.11-slim
WORKDIR /app

# Copy dependencies first (caches well)
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy application source
COPY src/ ./src/

CMD ["python", "src/app.py"]

Build now:

docker build -t myapp-lean .
# Sending build context to Docker daemon  24.5kB
# ...

The difference is dramatic: 24.5 kB vs 2.1 GB. That's a 99.998% reduction in context size.

Example 3: Fine-grained version – copy only individual files

FROM node:20-alpine
WORKDIR /app

# Copy only package files for dependency installation
COPY package.json package-lock.json ./
RUN npm install

# Copy only the source code
COPY src/ ./src/
COPY public/ ./public/

CMD ["npm", "start"]

Benefits: package.json and package-lock.json rarely change, so npm install is cached. Source code is copied separately, so changes to src/ don't invalidate the npm install layer.

Compare options / when to choose what

Approach When to use Pros Cons
COPY . /app + .dockerignore General-purpose web apps Simple; one-line COPY; .dockerignore acts as safety net Requires discipline to keep .dockerignore updated; might still copy extra files
COPY src/ /app/src/ (specific directories) Applications with clear folder structure (src/, lib/, static/) Very explicit; small context; easy to review More Dockerfile lines; must know exact paths
COPY requirements.txt . (single files) When dependencies change rarely Maximizes layer caching Only works when you can separate dependency files from source
Multi-stage builds Production optimizations (not this lesson) Ultimate image size control More complex; covered in later lessons

Rule of thumb: Start with .dockerignore and explicit directory copies. Only move to single-file COPY when you understand caching patterns.

Troubleshooting & edge cases

  • COPY fails with "no such file or directory" – you're trying to copy a file that isn't in the build context. Check your .dockerignore – it might be filtering it out. Run docker build -f Dockerfile . without .dockerignore temporarily to see what's in the context.
  • .dockerignore not working – make sure it's in the same directory as the Dockerfile, not inside a subfolder. Docker looks for {Dockerfile-dir}/.dockerignore.
  • .env file still ending up in the image – verify your .dockerignore pattern. For .env, the pattern **.env (double star) is safest in older Docker versions. In Docker 20.10+, .env works.
  • node_modules keeps being included – you might have node_modules/ off by one level. Ensure the pattern node_modules (without path) covers all subdirectories.
  • Build context is still large – run docker build . 2>&1 and look for the "Sending build context" line. If it's big, your .dockerignore is missing key directories.
  • Files changed but Docker uses cached layer – if you broke dependencies from source, changes in src/ won't invalidate the RUN pip install layer. That's by design, but if you did change requirements.txt, that file should be copied before the RUN.

What you learned & what's next

You now understand the core idea behind copy only needed files into images: use .dockerignore as a blocker and explicit COPY paths as selective packers. You completed a practical exercise that demonstrated how to shrink a 2.1 GB build context down to 24.5 kB.

Key takeaways: - Always include a .dockerignore in production projects. - Copy dependency files separately from source code for better caching. - Use explicit paths (COPY src/ /app/src/) instead of COPY .. - Verifying context size is easy — just read the first line of the build output.

Next up: Multi-stage builds — the final step in building truly production-optimized images. You'll take what you learned here and separate build-time dependencies from runtime artifacts, creating images that are even leaner and more secure.

Practice recap

Create a new project folder with a single app.py file and a requirements.txt containing two lines. Write a Dockerfile that copies only requirements.txt first, installs dependencies, then copies app.py separately. Add a .dockerignore that ignores a dummy .venv/ directory you create. Build the image and verify that the context size drops significantly compared to a sibling Dockerfile without .dockerignore. Check the layer count with docker history.

Common mistakes

  • Forgetting to include .dockerignore entirely — then wondering why the image is huge.
  • Copying package.json and package-lock.json separately but forgetting gitignore patterns, accidentally including the entire .git directory.
  • Using COPY . /app without a .dockerignore — the most common cause of 500+ MB images for simple Node or Python apps.
  • Assuming .dockerignore works recursively — it does, but only if patterns are correctly formatted (e.g., node_modules catches all nesting).

Variations

  1. Instead of a single .dockerignore, use COPY with --chown to change file ownership while copying selectively.
  2. Instead of .dockerignore, use multi-stage builds to copy only relevant artifacts from intermediate stages — especially for compiled languages.
  3. Instead of copying individual files, use a build-time .dockerignore generated by a CI script that knows exactly which files are needed.

Real-world use cases

  • Python web application: copy requirements.txt separately, then src/ only — virtualenv remains on host, reducing image size by 90%.
  • Node.js API: copy package.json and package-lock.json first, run npm ci, then copy src/ — node_modules stays inside image but excluded from context.
  • Static site generator: use .dockerignore to exclude .git, node_modules on build, then copy only dist/ output into a minimal nginx image.

Key takeaways

  • Always create a .dockerignore file in your project root to filter out unnecessary files from the build context.
  • Prefer explicit COPY instructions over COPY . — they make builds faster, smaller, and more secure.
  • Copy dependency files (requirements.txt, package.json) before source code to maximize layer caching.
  • Check the build context size in the first line of docker build output to spot bloat immediately.
  • .dockerignore patterns are relative to the Dockerfile directory — use node_modules (no slash) to match all nesting levels.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.