DevOpsLesson
DevOpsLesson

Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.

Stay Updated

Get notified about new tutorials and features.

Tutorials

  • What is DevOps?
  • Docker Tutorial
  • Terraform Tutorial
  • CI/CD Pipeline
  • All Tutorials

Roadmaps

  • DevOps Engineer
  • Cloud Engineer
  • SRE Path
  • All Roadmaps

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 DevOpsLesson. All rights reserved.

DOCKERKUBERNETESTERRAFORMAWSCI/CDLINUXGITDEVOPS ROADMAPCLOUD ROADMAPSRE ROADMAPGIT CHEATSHEETDOCKER CHEATSHEETK8S CHEATSHEETTF CHEATSHEETLINUX CHEATSHEETDOCKERFILE LINTERYAML VALIDATORCRON PARSERREGEX TESTER

Docker Tutorial

Introduction to docker
Why Use Docker?
Docker vs Virtual Machines
Installing Docker
Key Docker Concepts
Docker Images
Docker Containers
Writing Dockerfiles
Docker Volumes

COPY & ADD

PreviousPrev
Next

Copy files into your image correctly. Learn the difference between COPY and ADD, how to set ownership, use .dockerignore, and avoid common mistakes.

COPY & ADD

COPY and ADD both bring files from your host (the build context) into the image. COPY does one thing simply and explicitly. ADD does more - sometimes too much. Knowing when to use each prevents surprises.

COPY - The Default Choice

COPY copies files and directories from the build context into the image filesystem. What you see is what you get.

# Single file
COPY server.js /app/server.js

# Into a directory (trailing slash = directory target)
COPY server.js /app/

# Multiple sources into a directory
COPY package.json package-lock.json /app/

# Wildcard - all .json files
COPY *.json /app/

# Entire directory
COPY ./src /app/src

# Current directory into WORKDIR
COPY . .

Source paths are relative to the build context (the . in docker build -t myapp .). Destination paths are absolute inside the image, or relative to WORKDIR.

COPY with --chown

By default, copied files are owned by root. Use --chown to set ownership in the same layer - essential when running as a non-root user:

FROM node:20-alpine
WORKDIR /app

# Files owned by the node user (built into the node image)
COPY --chown=node:node package*.json ./
RUN npm ci
COPY --chown=node:node . .

USER node
CMD ["node", "server.js"]

Without --chown, the node user can't read or write files owned by root - a common cause of permission errors.

You can also use numeric UID/GID (more portable across images):

COPY --chown=1000:1000 . .

COPY with --chmod

Set file permissions directly:

# Make a script executable
COPY --chmod=755 entrypoint.sh /entrypoint.sh

# Read-only config
COPY --chmod=644 config.yaml /app/config.yaml

Multi-Stage COPY: --from

Copy files from a previous build stage or external image instead of the host:

FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
# Copy only the compiled output from the builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules

Copying Dependency Files First (Cache Pattern)

The most important COPY pattern for build speed - copy only the dependency manifest, install, then copy source code:

#  Optimized - npm ci only reruns when package.json changes
COPY package*.json ./
RUN npm ci
COPY . .

# Unoptimized - npm ci reruns every time any file changes
COPY . .
RUN npm ci

Apply the same pattern in every language:

# Python
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

# Go
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# Ruby
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
Exercise

COPY with Ownership

You are writing a Dockerfile based on node:20-alpine with WORKDIR /app. You want to copy package.json into the image owned by the "node" user, install dependencies, and then copy the rest of the source - also owned by "node". Which snippet is correct?

ADD - When to Use It

ADD has two capabilities beyond COPY:

1. Auto-extract local archives:

# .tar.gz, .tar.bz2, .tar.xz are auto-extracted
ADD app.tar.gz /app/
# Equivalent to: COPY app.tar.gz /tmp/ && tar xzf /tmp/app.tar.gz -C /app/

2. Download from URLs:

ADD https://example.com/config.json /app/config.json

However, the URL form has problems: it runs at build time (not cached well), can't verify the file's integrity, and creates a single layer with the file already present (can't clean up in the same layer). For URLs, prefer RUN curl or RUN wget instead:

#  Better than ADD for URLs - you can verify and clean up
RUN curl -fsSL https://example.com/install.sh | sh

#  Or download, verify, install, clean up in one layer
RUN curl -fsSL -o /tmp/tool.tar.gz https://example.com/tool.tar.gz && \
    echo "abc123  /tmp/tool.tar.gz" | sha256sum -c && \
    tar xzf /tmp/tool.tar.gz -C /usr/local/bin && \
    rm /tmp/tool.tar.gz

Rule of thumb: Use COPY by default. Only use ADD when you specifically need local archive auto-extraction.

The .dockerignore File

COPY . . copies everything in the build context unless you tell Docker otherwise. A .dockerignore file works exactly like .gitignore:

# .dockerignore

# Dependencies (rebuilt inside image anyway)
node_modules/
vendor/
__pycache__/
*.pyc
.venv/

# Version control
.git/
.gitignore

# Secrets and local config
.env
.env.*
*.pem
*.key

# Build artifacts
dist/
build/
*.egg-info/
target/

# IDE and OS files
.vscode/
.idea/
.DS_Store
Thumbs.db

# Test and docs (not needed at runtime)
tests/
test/
spec/
docs/
*.md
coverage/

Why it matters:

  • Prevents secrets from leaking into images
  • Dramatically reduces build context size (speeds up docker build)
  • Avoids cache invalidation from irrelevant file changes

Always create .dockerignore before writing COPY . ..

Common Mistakes

Copying too early:

# COPY . . before installing deps busts the cache every build
COPY . .
RUN npm install

#  Copy deps manifest first
COPY package*.json ./
RUN npm install
COPY . .

Forgetting .dockerignore:

# This copies node_modules (hundreds of MB) if .dockerignore is missing
COPY . .

Wrong destination syntax:

# This creates a FILE called /app (not a directory!)
COPY server.js /app

#  Trailing slash = directory
COPY server.js /app/

#  Or set WORKDIR first
WORKDIR /app
COPY server.js .

Summary

  • Use COPY by default - it's explicit and predictable
  • Use ADD only for local archive auto-extraction
  • --chown=user:group sets ownership in the same layer; always use it when running as non-root
  • Copy dependency manifests before source code to maximise layer cache reuse
  • .dockerignore is not optional - always create it before using COPY . .
PreviousPrev
Next

Continue Learning

Cleanup & Pruning

Keep your Docker environment clean. Remove stopped containers, reclaim disk space, and build automated cleanup habits.

6 min·Easy

Writing Dockerfiles

Learn to write Dockerfiles from scratch - every instruction explained, with patterns and pitfalls from real-world usage.

35 min·Easy

FROM

The FROM instruction starts every Dockerfile. Learn how to choose the right base image, use variants wisely, and unlock multi-stage builds.

8 min·Easy

Explore Related Topics

Ku

Kubernetes Tutorials

Orchestrate your Docker containers at scale

CI

CI/CD Tutorials

Automate Docker builds and deployments in pipelines

Try the Tool

Dockerfile Linter

Instantly lint your Dockerfile for best-practice violations and security issues.

On This Page

COPY & ADD`COPY` - The Default ChoiceCOPY with `--chown`COPY with `--chmod`Multi-Stage COPY: `--from`Copying Dependency Files First (Cache Pattern)`ADD` - When to Use ItThe `.dockerignore` FileCommon MistakesSummary