Back to Articles
DevOps Feb 14, 2026 · 9 min read

I cut my Docker build time from 15 minutes to 3 minutes

Daniel

Software Developer

Our Docker builds in CI took 15 minutes. Developers were waiting, and we were burning CI minutes. I spent a day optimizing, and here is what actually made a difference.

The Before

FROM node:20
WORKDIR /app
COPY . .               -- Copy everything
RUN npm install       -- Install dependencies (14 minutes)
RUN npm run build     -- Build (1 minute)
CMD ["npm", "start"]

Problem 1: node_modules was being copied every time

Every commit changed files, triggering full COPY and npm install.

FROM node:20-alpine
WORKDIR /app

# Copy package files FIRST (this is key!)
COPY package*.json ./
RUN npm ci --production

# Then copy the rest
COPY . .
RUN npm run build

CMD ["npm", "start"]

Problem 2: Using full node image

We switched to alpine - much smaller, faster to pull.

FROM node:20-alpine  -- 150MB instead of 1GB

Problem 3: Not using BuildKit

# Enable BuildKit
export DOCKER_BUILDKIT=1

# In docker-compose
build:
context: .
dockerfile: Dockerfile
cache_from:  -- Use GitHub Actions cache
- ghcr.io/yourorg/app:builder

Problem 4: Multi-stage builds

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["npm", "start"]

The Results

  • First build: 3 minutes (was 15)
  • Subsequent builds: ~30 seconds (with cache)
  • Image size: 200MB (was 1.2GB)

Summary

Order matters. Put things that change less frequently (like package.json) earlier in the Dockerfile. Use BuildKit. Use alpine images. Multi-stage builds are your friend.