1. Introduction
A Docker build failure in CI/CD stops your pipeline before a single byte of new code gets deployed. The error output from Docker builds is often compact and cryptic — a single line explaining that a layer failed, without the full context of why. Understanding how Docker builds work and knowing which errors map to which causes makes these failures much faster to resolve.
This guide covers failures specific to CI/CD environments — registry authentication, build context issues, BuildKit configuration, and environment-specific failures that don't reproduce locally. For Docker socket permission errors on Jenkins, see that dedicated guide.
2. Understanding Docker Build Failures
Docker builds execute a Dockerfile layer by layer. Each RUN, COPY, ADD, and FROM instruction creates a layer. If any layer fails, the build stops at that layer. The error is reported on the failing layer, but the root cause is often earlier — a missing file, a failed RUN command, or an environment variable that wasn't set.
3. Common Causes
- Registry authentication failure — credentials expired or not passed to the Docker build step
- Base image doesn't exist — wrong tag, image deleted, or private image without credentials
- COPY/ADD instruction fails — source file doesn't exist in the build context
- RUN command fails — package not found, network timeout, or non-zero exit from a script
- Build argument not defined — ARG referenced in Dockerfile but not passed with --build-arg
- Build context too large — .dockerignore missing, large files causing timeouts
- Multi-stage build: artifact from previous stage not found at expected path
- Architecture mismatch — building on ARM (M1/M2 Mac) but deploying to AMD64
4. Step-by-Step Fix
Step 1: Add verbose output to identify the exact failing layer
# Use --progress=plain to see all layer output
docker build --progress=plain -t my-image . 2>&1 | tee build.log
# In CI (GitLab example):
build:
script:
- docker build --progress=plain -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . 2>&1
# To build only up to the failing stage (multi-stage builds):
docker build --target build-stage -t debug-image .
Step 2: Fix registry authentication issues
# Log in before pulling or building
# GitLab CI (using built-in registry):
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
# GitHub Actions:
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# AWS ECR:
before_script:
- aws ecr get-login-password --region us-east-1 |
docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
Step 3: Fix COPY and ADD failures
# Error: COPY failed: file not found in build context
# The file path in COPY is relative to the build context (the . in "docker build .")
# Check what's in your build context (shows files that Docker sees)
docker build --no-cache -t test . 2>&1 | head -20
# Look for: "Sending build context to Docker daemon X MB"
# Create a .dockerignore file to exclude unnecessary files
cat > .dockerignore << 'EOF'
node_modules/
.git/
*.log
.env
dist/
coverage/
EOF
# Fix paths in your Dockerfile:
# Wrong: COPY ./build /app (when build/ doesn't exist yet)
# Right: COPY --from=builder /app/build /app (multi-stage: copy from builder)
Step 4: Fix RUN command failures
# Error: "The command '/bin/sh -c npm install' returned a non-zero code: 1"
# Run with --no-cache to see full output without cached layers
docker build --no-cache --progress=plain -t debug . 2>&1 | grep -A 20 "npm install"
# Common fixes:
# 1. Package not found: ensure base image version matches your dependencies
FROM node:20-alpine # match your local dev version
# 2. Network timeout: retry or use a mirror
RUN npm install --retry 3 --timeout 60000
# 3. Script fails silently: add set -e
RUN set -e && ./build.sh
# Debug a failing RUN by commenting out subsequent layers and running a shell:
# (temporarily add this to the end of Dockerfile)
# CMD ["sh"]
docker run --rm -it debug sh # then run the failing command manually
Step 5: Fix build argument issues
# Error: "ARG NPM_TOKEN is not defined"
# ARG must be defined before FROM in multi-stage builds if used in FROM
# Dockerfile:
ARG NPM_TOKEN # define the ARG
# Pass it during build:
docker build --build-arg NPM_TOKEN=$NPM_TOKEN -t my-image .
# In GitLab CI:
build:
script:
- docker build --build-arg NPM_TOKEN=$NPM_TOKEN -t $CI_REGISTRY_IMAGE .
# Note: ARG values are NOT available in the final image unless set as ENV
# If you need the value at runtime, use ENV:
ARG NPM_TOKEN
ENV NPM_TOKEN=$NPM_TOKEN # not recommended for secrets!
Step 6: Fix multi-stage build failures
"# Error: "COPY --from=build /app/dist /app/dist: file not found"
# The first stage failed to create the expected output
# Debug by building just the first stage:
docker build --target build -t debug-stage .
docker run --rm debug-stage ls /app/ # check if dist/ was created
# Common cause: build command failed silently
# Fix: make build errors explicit
RUN npm run build && test -d dist || (echo "Build failed, dist/ not found" && exit 1)
# Another common cause: wrong path assumption
COPY --from=build /app/dist ./ # copies contents of dist/
# vs
COPY --from=build /app/dist /app/dist/ # preserves dist/ directory structure
5. Verification Steps
# After fixing, build locally first
docker build -t my-image:test .
docker run --rm my-image:test echo "Container works"
# Test that the image contains expected files
docker run --rm my-image:test ls /app/dist/
# Push and confirm CI can pull it
docker push my-image:test
docker pull my-image:test && echo "Push/pull cycle works"
6. Common Mistakes
- Not having a
.dockerignorefile — largenode_modulesor.gitdirectories slow down builds and can cause context size errors - Using
latesttag for base images — a newlatestcan break your build unexpectedly; pin to a specific version - Running
npm installinstead ofnpm ciin Dockerfiles —npm ciis faster and more deterministic - Not separating package installation from source copy — cache the package layer independently so rebuilds don't reinstall dependencies
- Building on an ARM Mac and pushing to a registry without specifying platform — produces ARM images that won't run on AMD64 servers
7. Prevention Tips
- Always use a
.dockerignorefile — at minimum excludenode_modules,.git, and local config files - Pin base image versions:
FROM node:20.11.0-alpine3.19, notFROM node:latest - Structure Dockerfiles to maximise layer caching: copy package files first, install deps, then copy source code
- Use multi-platform builds with
docker buildxif deploying to different architectures - Add Docker build step to pre-commit hooks or a local
make buildtarget so failures are caught before pushing - Use Docker layer caching in CI to avoid runner timeout issues from rebuilding everything every time
8. FAQ
The Docker build works locally but fails in CI. Why?
The most common reasons: (1) CI runner doesn't have registry credentials — add a login step, (2) a local file exists that isn't in the repo — add it to .dockerignore or commit it, (3) local environment variable is set that CI doesn't have — pass it with --build-arg, (4) architecture difference — CI runs AMD64, local is ARM; add --platform linux/amd64.
How do I use Docker BuildKit secrets to avoid leaking credentials into the image?
Use the --secret flag with BuildKit: docker build --secret id=npmrc,src=$HOME/.npmrc . and reference it in the Dockerfile with RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm install. This mounts the secret file during that layer only — it never appears in the image or build history.
9. Summary
| Error pattern | Cause | Fix |
|---|---|---|
| unauthorized: authentication required | Not logged into registry | Add docker login step in before_script |
| COPY failed: not found | File not in build context | Fix .dockerignore or Dockerfile paths |
| RUN returned non-zero | Command failed inside build | Build with --no-cache --progress=plain; debug RUN manually |
| ARG not defined | --build-arg not passed | Pass ARG in docker build command |
| --from stage artifact not found | Previous stage failed | Build each stage separately to isolate failure |
Explore More in This Category
Explore more in this category: CI/CD guides. Browse all DevOps Compass articles or jump to: Kubernetes, AWS, CI/CD, Containers, Monitoring, Networking.