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

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

7. Prevention Tips

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 patternCauseFix
unauthorized: authentication requiredNot logged into registryAdd docker login step in before_script
COPY failed: not foundFile not in build contextFix .dockerignore or Dockerfile paths
RUN returned non-zeroCommand failed inside buildBuild with --no-cache --progress=plain; debug RUN manually
ARG not defined--build-arg not passedPass ARG in docker build command
--from stage artifact not foundPrevious stage failedBuild 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.