How to Fix Docker COPY Failed: stat no source files specified
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins
TL;DR
- What broke: Docker's
COPYinstruction received an empty or unresolvable source argument — the builder has zero files to copy and hard-stops the build. - How to fix it: Verify the source path exists relative to the build context directory, is not excluded by
.dockerignore, and is explicitly named in theCOPYinstruction. - Use our Client-Side Sandbox below to paste your Dockerfile and auto-refactor the broken
COPYinstruction instantly.
The Incident (What Does the Error Mean?)
Raw error output from docker build:
> [build 3/6] COPY /app:
------
COPY failed: stat no source files were specified
or the variant:
COPY failed: stat /var/lib/docker/tmp/buildkit-mount123456/src: no such file or directory
Docker's BuildKit evaluates the COPY instruction at build time by resolving each source path relative to the build context sent to the daemon. When the source argument is blank, a glob matches nothing, or the path sits outside the context root, BuildKit has nothing to stat() and throws a fatal error. The image layer is never written. The build exits non-zero. Your pipeline is dead.
The Attack Vector / Blast Radius
This is not a runtime error — it's a build-time hard failure, which means:
- Every downstream stage in a multi-stage build is skipped. A broken
COPYin stage 1 means your final production image is never produced. - CI/CD pipelines fail and block deployments. In a trunk-based workflow, a single bad Dockerfile commit can lock an entire release train.
- Silent
.dockerignoreexclusions are the most dangerous variant. A developer addssrc/to.dockerignoreto speed up local builds, pushes the change, and the remote CI runner — which has no cached layers — hits this error with zero obvious connection to the.dockerignorechange. Root-causing this across a team costs 30–90 minutes of wasted engineering time per occurrence. - Parameterized
COPYviaARGis a second common vector. If a--build-argis not passed at build time, theARGresolves to empty string, andCOPY $SRC_PATH /appbecomesCOPY /app— exactly the error above.
How to Fix It (The Solution)
Root Cause Checklist
Before touching code, run through these in order:
- Is the source path spelled correctly and relative to the build context?
COPY src/app.py /app/requiressrc/app.pyto exist at<build-context>/src/app.py. - Is the file excluded by
.dockerignore? Runcat .dockerignoreand check for glob patterns that swallow your source. - Is an
ARGused as the source and not passed via--build-arg? An unsetARGis an empty string. - Is the build context path correct in your
docker buildcommand?docker build -f infra/Dockerfile .vsdocker build -f infra/Dockerfile ./infra/— the trailing path is the context root.
Basic Fix — Empty or Mistyped Source
- COPY /app/
+ COPY src/ /app/
- COPY ./buid/output/ /app/
+ COPY ./build/output/ /app/
Enterprise Best Practice — ARG-Driven COPY with Validation
Never use a bare ARG as a COPY source without a default and a guard.
- ARG SOURCE_DIR
- COPY $SOURCE_DIR /app/
+ ARG SOURCE_DIR=src
+ # Validate ARG is non-empty before COPY
+ RUN test -n "$SOURCE_DIR" || (echo "ERROR: SOURCE_DIR build-arg is required" && exit 1)
+ COPY ${SOURCE_DIR}/ /app/
Multi-stage build — ensure context files are present before the COPY stage:
FROM node:20-alpine AS builder
WORKDIR /build
- COPY . .
- RUN npm ci && npm run build
+ # Be explicit. Never COPY the entire context blindly.
+ COPY package.json package-lock.json ./
+ RUN npm ci
+ COPY src/ ./src/
+ RUN npm run build
FROM nginx:1.25-alpine
- COPY --from=builder /build/dist /usr/share/nginx/html
+ COPY --from=builder /build/dist/ /usr/share/nginx/html/
Fix a .dockerignore over-exclusion:
- src/
+ # src/ removed — required by COPY src/ /app/
+ node_modules/
+ .git/
+ *.log
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Hadolint in Pre-Commit and CI
Hadolint statically lints Dockerfiles and catches empty COPY sources before a build is ever triggered.
# .github/workflows/lint.yml
- name: Lint Dockerfile
uses: hadolint/[email protected]
with:
dockerfile: Dockerfile
failure-threshold: warning
2. Checkov Policy for ARG-Sourced COPY
checkov -d . --check CKV_DOCKER_2
Write a custom Checkov check if your org uses ARG-driven paths to enforce non-empty defaults.
3. Build Context Audit Script (Drop into CI)
#!/usr/bin/env bash
# Fail fast if any COPY source in the Dockerfile doesn't exist in the build context
set -euo pipefail
DOCKERFILE=${1:-Dockerfile}
CONTEXT=${2:-.}
grep -E '^COPY' "$DOCKERFILE" | while read -r line; do
# Extract source tokens (all args except last, which is dest)
args=($line)
unset 'args[0]' # remove COPY keyword
unset 'args[${#args[@]}-1]' # remove destination
for src in "${args[@]}"; do
[[ "$src" == --* ]] && continue # skip flags like --from
if [[ -z "$src" ]]; then
echo "FATAL: Empty COPY source in: $line" && exit 1
fi
if ! ls "${CONTEXT}/${src}" &>/dev/null; then
echo "FATAL: COPY source not found in context: ${CONTEXT}/${src}" && exit 1
fi
done
done
echo "All COPY sources validated."
4. .dockerignore Change Detection Gate
In your CI pipeline, if .dockerignore is modified in a PR, automatically trigger a full docker build --no-cache to catch newly excluded paths before merge.
# GitLab CI example
docker-build-validation:
rules:
- changes:
- .dockerignore
- Dockerfile
script:
- docker build --no-cache -t validation-build:$CI_COMMIT_SHORT_SHA .