🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Docker Build Fails in CI But Works Locally — Fix

Your Docker build works perfectly on your machine but fails in GitHub Actions, GitLab CI, or Jenkins. Here's every reason this happens and exactly how to fix it.

DevOpsBoysMay 30, 20263 min read
Share:Tweet

"Works on my machine" — the most frustrating phrase in DevOps. Your Docker build succeeds locally, the image runs fine, but CI fails every time.

Here's every reason this happens.


Quick Diagnosis

bash
# In CI, add this before your docker build to see what's different
docker info
docker version
echo "Platform: $(uname -m)"
echo "Docker buildx: $(docker buildx version)"

Case 1: Different Platform (arm64 vs amd64)

If you're on a Mac M1/M2/M3 (arm64) and CI runs on amd64 Linux, images built locally won't work in CI.

Symptom:

exec /usr/local/bin/node: exec format error

Fix: Build for the correct platform in CI:

yaml
# GitHub Actions
- name: Build Docker image
  run: docker build --platform linux/amd64 -t myapp .

Or use buildx for multi-platform:

yaml
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3
 
- name: Build
  uses: docker/build-push-action@v5
  with:
    platforms: linux/amd64,linux/arm64
    tags: myapp:latest

Case 2: Build Context Too Large / .dockerignore Missing

Locally you have a warm Docker cache. In CI, everything is fresh and your build context might be huge.

Symptom: Build hangs at "Sending build context" for minutes.

Fix: Add .dockerignore:

# .dockerignore
node_modules
.git
.github
*.log
dist
.env
*.env
coverage
.DS_Store

Check context size:

bash
# See what's being sent to Docker daemon
du -sh $(ls -A | grep -v .git) 2>/dev/null | sort -hr | head -20

Case 3: Cache Not Available in CI

Your local build is fast because Docker caches layers. CI starts fresh — every build downloads everything.

Fix: Use GitHub Actions cache or registry cache:

yaml
# GitHub Actions with layer caching
- name: Build and push
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: myapp:latest
    cache-from: type=gha          # GitHub Actions cache
    cache-to: type=gha,mode=max

Or use registry cache:

yaml
cache-from: type=registry,ref=myregistry/myapp:buildcache
cache-to: type=registry,ref=myregistry/myapp:buildcache,mode=max

Case 4: Missing Environment Variables / Build Args

You have .env locally but CI doesn't:

Symptom:

ERROR: "DB_URL" is required but not set

Fix: Pass build args in CI:

yaml
# GitHub Actions
- name: Build
  run: |
    docker build \
      --build-arg NODE_ENV=production \
      --build-arg API_URL=${{ secrets.API_URL }} \
      -t myapp .

In Dockerfile:

dockerfile
ARG NODE_ENV
ARG API_URL
ENV NODE_ENV=$NODE_ENV

Never put secrets in image layers. Use --secret for sensitive values:

bash
docker build --secret id=mysecret,env=MY_SECRET .
dockerfile
# In Dockerfile
RUN --mount=type=secret,id=mysecret \
    cat /run/secrets/mysecret && npm install

Case 5: Network Restrictions in CI

CI runners often have restricted network access. Package downloads that work locally fail in CI.

Symptom:

npm ERR! network timeout at: https://registry.npmjs.org/...
E: Failed to fetch http://archive.ubuntu.com/...

Fixes:

Use a private registry mirror:

dockerfile
# For npm
RUN npm install --registry https://your-nexus.internal/npm/
 
# For apt
RUN echo "deb http://your-mirror.internal/ubuntu focal main" > /etc/apt/sources.list

Or cache dependencies in the Dockerfile correctly:

dockerfile
# Copy package.json FIRST (before source code)
# This layer is cached unless dependencies change
COPY package*.json ./
RUN npm ci --only=production
 
# THEN copy source
COPY . .

Case 6: Different Docker Version

CI might use an older Docker that doesn't support features you use locally.

Symptom:

unknown flag: --mount
# or
OCI runtime create failed

Fix: Pin Docker version in CI:

yaml
# GitHub Actions — use specific buildx version
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3
  with:
    version: v0.12.0

Or check what syntax features need a specific version:

dockerfile
# syntax=docker/dockerfile:1.5  ← requires BuildKit
# If CI doesn't have BuildKit enabled:
DOCKER_BUILDKIT=1 docker build .

Enable BuildKit in CI:

yaml
env:
  DOCKER_BUILDKIT: 1

Case 7: File Permission Issues

Linux CI has different default umask than Mac:

Symptom:

permission denied: ./entrypoint.sh

Fix:

dockerfile
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh  # Explicitly set permissions

Or set in COPY:

dockerfile
COPY --chmod=755 entrypoint.sh .

Reproduce CI Locally

Test your CI environment locally using act:

bash
# Install act (runs GitHub Actions locally)
brew install act  # or download binary
 
# Run CI workflow locally
act push
 
# Run specific job
act -j build

This runs the exact same Docker environment as GitHub Actions on your machine.


CI Docker Build Checklist

  • Add .dockerignore
  • Set --platform linux/amd64 if on Apple Silicon
  • Enable DOCKER_BUILDKIT=1
  • Add layer caching (cache-from/cache-to)
  • Pass all required build args as CI secrets
  • Test with act before pushing

Learn Docker best practices with hands-on labs at KodeKloud — their Docker course covers multi-stage builds, BuildKit, and CI/CD integration.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments