Initializing Enclave...

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 COPY instruction 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 the COPY instruction.
  • Use our Client-Side Sandbox below to paste your Dockerfile and auto-refactor the broken COPY instruction 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 COPY in 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 .dockerignore exclusions are the most dangerous variant. A developer adds src/ to .dockerignore to 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 .dockerignore change. Root-causing this across a team costs 30–90 minutes of wasted engineering time per occurrence.
  • Parameterized COPY via ARG is a second common vector. If a --build-arg is not passed at build time, the ARG resolves to empty string, and COPY $SRC_PATH /app becomes COPY /app — exactly the error above.

How to Fix It (The Solution)

Root Cause Checklist

Before touching code, run through these in order:

  1. Is the source path spelled correctly and relative to the build context? COPY src/app.py /app/ requires src/app.py to exist at <build-context>/src/app.py.
  2. Is the file excluded by .dockerignore? Run cat .dockerignore and check for glob patterns that swallow your source.
  3. Is an ARG used as the source and not passed via --build-arg? An unset ARG is an empty string.
  4. Is the build context path correct in your docker build command? docker build -f infra/Dockerfile . vs docker 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 .

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →