Caching DevContainer Builds in CI

Rebuilding a devcontainer from scratch on every CI run is slow and wasteful. This page caches the build with a registry layer cache (cacheFrom/cacheTo) and persists package-manager stores, so a pipeline reuses prior work and the environment is ready in seconds.

Prerequisites

You need a registry the CI runner can push/pull and a devcontainer build.

  • A registry (GHCR, ECR, etc.) reachable from CI.
  • The devcontainers/ci action or the CLI with BuildKit.
  • Lockfiles committed for keying dependency caches.

Cache prerequisitesYou need registry cache pull/push and a dependency cache keyed on the lockfile.cacheFrompull layerscacheTopush layersDep cachekeyed on lockfileVerifyfast reuse

Step-by-Step Implementation

  1. Enable the registry layer cache.
      - uses: devcontainers/ci@v0.3
        with:
          cacheFrom: ghcr.io/acme/dev-image
          push: always     # publish cacheTo for the next run
  1. Order the Dockerfile so stable layers cache well.
# system packages (rarely change) BEFORE app deps (change often)
RUN apt-get update && apt-get install -y --no-install-recommends git curl
  1. Cache dependencies keyed on the lockfile.
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('package-lock.json') }}
  1. Verify a second run reuses layers (much faster).

CI cache anatomyA registry layer cache plus a lockfile-keyed dependency cache makes CI builds fast.cacheFromreuse prior layersLayer orderstable-firstDependency cachekeyed on lockfileResultseconds, not minutes

Common Pitfalls

Cache misses come from unordered layers, no registry cache, or a coarse key.

Cache triageA triage path from full rebuilds to reused layers and dependency caches.Does a second run reuse layers?NOAdd cacheFrom/cacheToIs the dep cache keyed on the lockfile?YESOrder layers stable-firstFast, correct CI cache

SymptomRoot CauseRemediation
Full rebuild every runNo registry layer cacheSet cacheFrom and cacheTo
Small change busts the whole cacheApp deps layered too earlyPut stable layers first
Cache serves stale depsKey not tied to the lockfileKey on the lockfile hash
Cache never populatespush disabledPublish cacheTo on main builds

Conclusion

CI caching is two layers: a registry image cache so Docker reuses build layers, and a lockfile-keyed dependency cache so package installs reuse downloads. Order the Dockerfile stable-first, and a CI build goes from minutes to seconds while staying reproducible.

Two cache typesA registry layer cache and a lockfile-keyed dependency cache work together.Layer cachecacheFrom/cacheToStable-first orderRegistry imageDep cacheLockfile keyPackage store pathRestore + save

FAQ

What's the difference between the layer cache and the dependency cache? The layer cache reuses Docker build layers (the image build itself) via cacheFrom/cacheTo. The dependency cache reuses downloaded packages (npm, pip, etc.) keyed on your lockfile. Both help; the layer cache saves image build time, the dependency cache saves install time.

Why does a tiny change rebuild everything? Because a frequently-changing layer sits early in the Dockerfile, invalidating everything after it. Put rarely-changing layers (system packages) first and application dependencies last, so a small change invalidates as little as possible.

How do I keep a cache from serving stale dependencies? Key the dependency cache on the lockfile hash (hashFiles('package-lock.json')). Then the cache is only reused when the resolved dependency set is unchanged, so it can never smuggle in a stale package.