This Dockerfile error occurs when COPY or ADD instructions use multiple source files or wildcard patterns but the destination path doesn't end with a trailing slash. Docker requires the destination to be explicitly marked as a directory when copying multiple files.
The "COPY failed: when using more than one source file, destination must be a directory" error occurs during Docker image builds when you attempt to copy multiple files to a destination that Docker interprets as a single file rather than a directory. In Docker, the COPY and ADD instructions can accept multiple source files or wildcard patterns (like `*.go` or `package*.json`). When multiple files are being copied, Docker needs to know that the destination is a directory where all these files should be placed. The way to signal this is by ending the destination path with a trailing slash (`/`). Without the trailing slash, Docker assumes the destination is a file path. Since you can't copy multiple files into a single file, Docker throws this error to prevent data loss and ambiguity. This behavior was intentionally added to Docker's build process to support multiple sources in COPY/ADD instructions while preventing accidental file overwrites.
The most common fix is adding a trailing slash (/) to the destination path to explicitly indicate it's a directory.
Incorrect:
COPY *.go .
COPY package*.json .Correct:
COPY *.go ./
COPY package*.json ./The trailing slash tells Docker that the destination is a directory, not a file. All matched source files will be copied into this directory.
Instead of relative paths, use absolute directory paths ending with a slash for clarity:
# Copy all JavaScript files to /app/ directory
COPY *.js /app/
# Copy configuration files to a config directory
COPY *.conf /etc/myapp/
# Copy multiple specific files to application directory
COPY package.json package-lock.json /app/This approach makes the Dockerfile more readable and avoids ambiguity about where files are being placed.
Check what files your pattern actually matches in the build context:
# List files matching a pattern
ls -la *.go
ls -la package*.json
# Check build context contents
ls -la .If only one file matches the pattern, the COPY instruction might work without the trailing slash. However, if another file is added later that matches the pattern, the build will break. Always use trailing slashes for wildcard patterns to future-proof your Dockerfile.
If copying to a specific directory, ensure it exists or create it with WORKDIR or RUN:
# Using WORKDIR (preferred)
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
# Or explicitly create directory
FROM node:18-alpine
RUN mkdir -p /app/config
COPY *.conf /app/config/WORKDIR automatically creates the directory if it doesn't exist and sets it as the working directory, making relative paths like ./ refer to that directory.
When copying multiple explicitly named files (not wildcards), you must also use a directory destination:
Incorrect:
COPY file1.txt file2.txt file3.txt /destinationCorrect:
COPY file1.txt file2.txt file3.txt /destination/Alternatively, use multiple COPY instructions for more control:
COPY file1.txt /destination/file1.txt
COPY file2.txt /destination/file2.txtNote: Multiple COPY instructions create multiple layers, which may increase image size slightly.
Docker BuildKit supports the --parents flag to preserve source directory structure:
# syntax=docker/dockerfile:1.7
FROM python:3.11
WORKDIR /app
# Copy preserving directory structure
COPY --parents src/**/*.py ./
COPY --parents config/ tests/ ./This copies files while maintaining their relative paths from the build context. The syntax comment at the top enables the latest Dockerfile features.
Note: This requires Docker BuildKit and Dockerfile syntax version 1.7 or later.
Why Docker Requires This Syntax:
The requirement for a trailing slash when copying multiple files was intentionally designed to prevent accidental data loss. Without this safeguard, copying multiple files to what Docker interprets as a single file path would silently overwrite files, with only the last file surviving.
COPY vs ADD:
Both COPY and ADD instructions have this same requirement. However, COPY is preferred for most use cases as it's more transparent. ADD has additional features (URL downloading, automatic tar extraction) that are rarely needed and can introduce unexpected behavior.
Layer Optimization:
Each COPY instruction creates a new layer in the Docker image. To minimize layers and image size, combine related file copies:
# Fewer layers (preferred)
COPY package.json package-lock.json tsconfig.json ./
# More layers (avoid)
COPY package.json ./
COPY package-lock.json ./
COPY tsconfig.json ./Build Context Considerations:
Files in your .dockerignore won't be copied even if they match the pattern. If your COPY instruction unexpectedly fails or copies fewer files than expected, check your .dockerignore file:
cat .dockerignoreMulti-stage Build Copying:
When using COPY --from=<stage> in multi-stage builds, the same trailing slash rule applies:
FROM golang:1.21 AS builder
COPY *.go ./
RUN go build -o /app
FROM alpine:3.18
# Trailing slash required if copying multiple files from builder
COPY --from=builder /app /usr/local/bin/dockerfile parse error line 5: unknown instruction: RRUN
How to fix 'unknown instruction' Dockerfile parse error in Docker
Error response from daemon: manifest for nginx:nonexistent not found: manifest unknown: manifest unknown
How to fix 'manifest for image:tag not found' in Docker
Error response from daemon: invalid reference format: repository name must be lowercase
How to fix 'repository name must be lowercase' in Docker
Error response from daemon: No such image
How to fix 'No such image' in Docker
Error response from daemon: Container is not running
How to fix 'Container is not running' when using docker exec