Back to Blog
Performance10 min read2026-07-07

How to Reduce Build Times for Large Apps

Slow builds tax every commit. This guide covers the highest-leverage techniques to speed up large-app builds: layer caching, cache mounts, dependency hygiene, parallelism, and trimming the build context.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

A slow build is a tax you pay on every single commit — multiply a 10-minute build by dozens of pushes a day across a team and it's real money and real lost focus. The good news: most slow builds are slow for a handful of fixable reasons. This guide ranks the highest-leverage fixes for large-app builds.

First, measure where the time goes

Don't optimize blind. Get a per-step timing breakdown. With BuildKit:

DOCKER_BUILDKIT=1 docker build --progress=plain -t myapp .

The plain output shows how long each step takes. Almost always, one or two steps dominate — usually dependency installation or asset compilation. Optimize those; ignore the 200 ms steps.

Technique 1: Order layers for caching (highest leverage)

This is the single biggest win and the most commonly botched. Docker/BuildKit caches each layer; a layer's cache is invalidated when it or any layer before it changes. The mistake:

# BAD: copying everything before installing
COPY . .
RUN npm ci          # re-runs on ANY source change

Here, changing a single source file busts the COPY . . layer, which busts npm ci — so you reinstall all dependencies on every commit. The fix is to copy dependency manifests first:

# GOOD: deps cached across source changes
COPY package*.json ./
RUN npm ci          # cached unless package*.json changes
COPY . .            # source changes only bust from here down

Now npm ci runs only when dependencies actually change. For a large dependency tree, this alone can cut minutes per build.

Technique 2: Cache mounts for package managers

Even when a dependency install must re-run, you can avoid re-downloading everything using BuildKit cache mounts — a persistent cache that survives across builds without bloating the image:

# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm npm ci

The same pattern works across ecosystems:

EcosystemCache mount target
npm/root/.npm
pnpm/root/.local/share/pnpm/store
pip/root/.cache/pip
Go/root/.cache/go-build, /go/pkg/mod
Maven/root/.m2
apt/var/cache/apt

This is especially valuable in ephemeral-build-pod architectures (like PandaStack's) where there's no persistent local disk between builds — the cache mount provides the speed of caching without persisting pod state.

Technique 3: Shrink the build context

The build context is everything sent to the builder. If you accidentally ship node_modules, .git, build outputs, and large assets, you waste time just transferring them. Use .dockerignore:

# .dockerignore
node_modules
.git
dist
*.log
coverage
.next

A bloated context can add seconds-to-minutes before the build even starts, and can subtly bust caches. This is a 30-second fix with outsized payoff.

Technique 4: Multi-stage builds

Multi-stage builds let you keep heavy build tooling out of the final image and let BuildKit prune stages that don't contribute to your target:

FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

Bonus: a smaller final image also reduces *deploy* time and cold starts, not just build time.

Technique 5: Exploit parallelism

BuildKit builds independent stages in parallel automatically (it models the build as a DAG). You can help it by structuring independent work into separate stages — e.g. building a frontend and backend in parallel stages that merge at the end. Don't force artificial serialization between things that don't depend on each other.

Technique 6: Dependency hygiene

Sometimes the build is slow because you're installing too much:

  • Prune unused dependencies. Large node_modules installs slowly. Audit periodically.
  • Use production installs in the final stage (npm ci --omit=dev) so dev tooling isn't installed where it isn't needed.
  • Commit a lockfile. Beyond determinism, lockfiles let package managers skip resolution work.
  • Watch postinstall scripts. A heavy postinstall (downloading binaries, compiling native modules) can dominate. Cache or eliminate it.

Technique 7: Right-size build resources

A build starved of CPU or memory is slow — or fails outright. The classic symptom is exit code 137 (OOM-killed) during a memory-hungry bundling step (large webpack/Vite builds, TypeScript compilation). If your build OOMs or crawls, give it a larger build tier. This is the one case where throwing resources at the problem is the correct fix.

Priority order (do these in sequence)

  1. 1.dockerignore — 30 seconds, immediate.
  2. 2Layer ordering — copy manifests before source.
  3. 3Cache mounts — for the package manager.
  4. 4Multi-stage build — smaller image, faster deploy too.
  5. 5Dependency hygiene — prune, prod-only, lockfile.
  6. 6Resources — only if measurement shows you're starved.

Measure after each change. The first two fixes alone resolve the majority of slow builds.

How PandaStack helps

PandaStack builds with rootless BuildKit in ephemeral pods, so you automatically get BuildKit's DAG parallelism and layer caching, plus registry-backed and cache-mount caching that works even though build pods are throwaway. You also get live, searchable build logs (self-hosted Elasticsearch) with the --progress=plain-style per-step output you need to find your slow step, and the ability to bump the build tier if a step is resource-starved.

References

  • [Docker build cache](https://docs.docker.com/build/cache/)
  • [BuildKit cache mounts](https://docs.docker.com/build/cache/optimize/#use-cache-mounts)
  • [Dockerfile best practices](https://docs.docker.com/build/building/best-practices/)
  • [Multi-stage builds](https://docs.docker.com/build/building/multi-stage/)
  • [.dockerignore reference](https://docs.docker.com/reference/dockerfile/#dockerignore-file)

---

PandaStack gives you BuildKit caching, parallel builds, and per-step build logs out of the box — plus 300 build minutes/month on the free tier to tune your Dockerfile against. Push a repo at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Performance

Browse all Performance articles →

See also