Local microservice stacks are great for testing interactions, but slow image builds and noisy file-syncing can turn your inner loop into a drag. Two modern Compose-era features give you high leverage: the Compose “develop” section (watch/sync/rebuild) that targets development workflows, and BuildKit cache mounts (RUN –mount=type=cache) that dramatically cut rebuild time for package installs and compiles. This article explains how to combine them safely with compose volumes and bind mounts to get faster, more reliable local iteration. (docs.docker.com)

What the Compose “develop” section gives you

Compose now includes an optional develop subsection for services. When enabled it lets Compose watch paths for changes and take actions tailored for development: rebuild an image, restart a container, synchronize files into a running container, or synchronize then exec a command. These actions let you update code without rebuilding everything or losing service state. The feature is available in Docker Compose v2.22.0 and later. (docs.docker.com)

Highlights:

The Compose CLI also exposes watch-related commands and profile mechanisms to combine dev-only services (e.g., debuggers) with production-like services. (docs.docker.com)

Use BuildKit cache mounts to speed image rebuilds

BuildKit provides the RUN –mount=type=cache feature inside Dockerfiles. For language ecosystems where dependency installs dominate build time (npm, pip, gem, go build cache), cache mounts let those directories persist across builds without baking them into the image. That speeds rebuilds and reduces network fetches. Typical use: RUN –mount=type=cache,target=/root/.cache/npm npm ci (or the appropriate cache path for your package manager). (github.com)

Important implementation note: cache mounts are stored by the builder locally and usually are not part of exported cache layers (so CI builders that start fresh each run may see empty cache mounts unless you configure cache export/import). Use cache mounts mainly to speed local developer builds where the same builder/runner is reused. (github.com)

Avoid common pitfalls with bind mounts and node_modules

A common stumbling block: a host bind mount over a non-empty directory in the image will obscure the container’s existing contents. That means an npm install done during image build can appear to “disappear” when you bind-mount your project root over /app. The engine behavior is normal, but surprising if you expect the built node_modules to stay available. (docs.docker.com)

Workarounds:

Named volumes are persistent and created by Compose on demand; Compose’s volumes model is designed for this kind of separation between host code and container-managed runtime files. (docs.docker.com)

Minimal example: compose + develop + BuildKit cache

Example docker-compose.yml (illustrative):

services:
  frontend:
    build: ./frontend
    develop:
      watch:
        - path: ./frontend/src
          action: sync
          target: /app/src
          ignore:
            - node_modules/
        - path: ./frontend/public
          action: sync
          target: /app/public
    volumes:
      - ./frontend:/app:delegated
      - frontend-node-modules:/app/node_modules

  backend:
    build: ./backend
    develop:
      watch:
        - path: ./backend
          action: rebuild
    volumes:
      - ./backend:/srv/app

volumes:
  frontend-node-modules:

Example Dockerfile (uses BuildKit cache mounts):

# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app

# cache npm cache between builds
RUN --mount=type=cache,target=/root/.npm \
    npm ci --silent

COPY . .
CMD ["npm", "start"]

Notes:

Quick tips and caveats

Closing

Combining Compose’s develop model (fine-grained watch and sync rules) with BuildKit cache mounts gives you an efficient local inner loop: small file edits get synchronized into running services, package installs are cached between builds, and heavyweight rebuilds are reserved for real changes. Use named volumes for runtime-only directories (like node_modules) so bind mounts don’t accidentally mask built artifacts, and remember that cache mounts are a local optimization — treat CI cache strategy separately. (docs.docker.com)