Docker 8 min read

Docker Build Failed? Fix Every Common Error

COPY file not found, pip install running on every build, exec format error on Apple Silicon, ports not reachable — each failure has a fast fix.

Which error are you seeing?

📂

COPY failed: file not found

The path you gave COPY does not exist inside the build context. Usually caused by running docker build from the wrong directory, or trying to copy files from outside the context.

pip install / npm install re-runs every time

Your COPY order is busting the layer cache. Copying all source before installing forces a full reinstall on any file change — even a one-line edit.

💻

exec /bin/sh: exec format error

Architecture mismatch. Your Mac (Apple Silicon) built an arm64 image, but your server expects amd64. Add --platform linux/amd64 to docker build.

🔒

Port not accessible after container starts

EXPOSE in a Dockerfile is documentation only — it does not open any port. You must map the port with -p when running the container.

Fix 1 — COPY failed: file not found in build context

The full error looks like: COPY failed: file not found in build context or excluded by .dockerignore: stat requirements.txt: file does not exist

1. Always run docker build from the repo root

The last argument to docker build is the build context directory. Docker sends that entire directory to the daemon. COPY paths are relative to it — not to where your Dockerfile lives.

# Wrong: building from a subdirectory makes ../app invisible
cd docker/
docker build -t myapp .

# Correct: build from repo root, point -f at the Dockerfile
cd /path/to/repo
docker build -f docker/Dockerfile -t myapp .

2. Add a .dockerignore to exclude large directories

Without a .dockerignore, Docker sends node_modules, .git, and build artifacts to the daemon — slowing every build and sometimes causing COPY conflicts. This file belongs in the same directory as your Dockerfile (or repo root).

# .dockerignore
node_modules
.git
.env
*.log
dist
__pycache__
.pytest_cache
.venv

Fix 2 — Stop pip install from running on every build

Docker processes Dockerfile instructions top-to-bottom and caches each layer. The moment a layer changes, every layer below it rebuilds from scratch. The most common mistake is copying all source code before running the install step.

Bad — copies everything first, busts cache on any file change

FROM python:3.12-slim
WORKDIR /app
COPY . .                          # any src change invalidates this layer
RUN pip install -r requirements.txt  # re-runs every time!

Good — copy manifests first, install, then copy source

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .           # only changes when deps change
RUN pip install --no-cache-dir -r requirements.txt
COPY . .                          # source changes do NOT bust install cache

The same pattern applies to Node.js: copy package.json and package-lock.json, run npm ci, then copy everything else.

Fix 3 — Multistage builds: keep the final image small

A multistage build compiles or installs in a full image, then copies only the output into a slim runtime image. The build tools (compilers, dev dependencies, pip) never appear in the final layer.

# Stage 1: build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build          # output goes to /app/dist

# Stage 2: slim runtime (no node_modules, no dev tools)
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

The AS builder label lets the second stage reference the first with --from=builder. You can have as many stages as needed; only the last one ships.

Fix 4 — exec /bin/sh: exec format error (Apple Silicon)

On M1/M2/M3 Macs, Docker defaults to linux/arm64. Most cloud servers (EC2, GKE, Fly.io) run linux/amd64. The result is a binary that the server cannot execute.

Option A — build for amd64 explicitly (simplest)

docker build --platform linux/amd64 -t myapp:latest .
docker push myapp:latest

Option B — multi-platform manifest with Buildx (CI/CD)

docker buildx create --use
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp:latest \
  --push .

Buildx produces a manifest list: the registry serves the correct image for each architecture automatically. This is the recommended approach for images you push to Docker Hub or GitHub Container Registry.

Fix 5 — ARG vs ENV: build-time vs runtime variables

ARG and ENV look similar but serve different purposes:

Instruction Available during build Available at runtime Set via
ARG Yes No --build-arg KEY=val
ENV Yes (after the line) Yes -e KEY=val or docker-compose env
ARG NODE_VERSION=20          # only during build
FROM node:{NODE_VERSION}-slim  # uses ARG value

ARG APP_VERSION
ENV APP_VERSION={APP_VERSION}  # promote ARG → ENV so app can read it at runtime
ENV NODE_ENV=production        # always set at runtime

Never put secrets in ENV — they are visible via docker inspect and baked into image layers. Use Docker secrets or a secrets manager at runtime instead.

Fix 6 — Port binding: EXPOSE does nothing by itself

EXPOSE 3000 in a Dockerfile is metadata that documents which port the container listens on. It does not actually open or map the port. Use -p in docker run to map it to the host.

# Maps host port 8080 → container port 3000
docker run -p 8080:3000 myapp

# Bind only on localhost (not exposed to network)
docker run -p 127.0.0.1:3000:3000 myapp

# Let Docker pick a random host port
docker run -p 3000 myapp
docker port myapp  # see what port was assigned

In docker-compose, port mapping is in the ports: section: "8080:3000". The format is always HOST:CONTAINER.

🔔

Get alerts when your infrastructure services go down

Track Docker Hub, GitHub Container Registry, and 200+ developer tools on Prismix. Get emailed the moment a service has an incident. Free.

Frequently asked questions

Why does Docker say COPY failed: file not found in build context?

The path in your COPY instruction is relative to the build context directory, not to where your Dockerfile is located. Run docker build from the repo root and use a .dockerignore to exclude node_modules and .git. If your project spans multiple directories, you may need to restructure or use a -f flag with the context set to a common parent.

Why does pip install or npm install re-run every build even when dependencies did not change?

Docker invalidates layer cache at the first changed instruction. If you COPY . . before running pip install, any source file change forces a reinstall. Fix: copy requirements.txt (or package.json) first, run the install, then COPY the rest of your source. Only a change to the manifest file will bust the install cache.

What causes exec /bin/sh: exec format error in Docker?

Architecture mismatch between build and runtime. Apple Silicon Macs produce linux/arm64 images by default. x86 servers cannot execute arm64 binaries. Add --platform linux/amd64 to your docker build command, or use docker buildx build with --platform linux/amd64,linux/arm64 for a multi-platform image.

What is the difference between ARG and ENV in a Dockerfile?

ARG is build-time only: it exists during docker build and is passed with --build-arg. It does not appear in the running container. ENV is baked into the image and available to the container at runtime via os.environ (Python) or process.env (Node.js). To expose an ARG value at runtime, assign it to an ENV: ENV MY_VAR=${MY_ARG}.

Related guides