Common Docker Compose Mistakes and How to Fix Them
This article examines common pitfalls when using Docker Compose for containerized development, such as frequent rebuilds, slow host volumes, fragile configurations, and resource mismanagement, and provides practical solutions including caching strategies, host volume tuning, env files, overrides, and resource allocation tips.
When building a containerized application, developers need a way to launch the containers they are using for testing their code. Docker Compose is a popular choice because it lets you easily specify the containers required during development and creates a fast "code‑test‑debug" loop.
The vision is that a developer writes a docker-compose.yml file that defines everything needed for development, commits it to the repository, and every other developer can simply run docker-compose up to start all the containers required for testing.
However, achieving optimal performance with Docker Compose often requires a lot of work. The best teams can start their development environment in under a minute and test each change in seconds, which dramatically improves developer productivity.
Error 1: Frequent container rebuilds
Running docker build takes a long time. If you rebuild the container for every code change, you lose a lot of speed. Traditional non‑container workflows follow the steps: code → build → run. Over the years, incremental builds and hot‑loading have made this process very fast for compiled languages.
When teams first adopt containers they often add a docker build step to the existing workflow, resulting in: code → build → container build → run. If not done carefully, the docker build step defeats all the optimizations and adds extra work such as reinstalling dependencies with apt‑get , making testing slower than before.
Solution: Run your code outside Docker
One approach is to start all dependencies with Docker Compose but run the code you are actively working on locally, exposing the dependencies on localhost and pointing your services to localhost:<port> . This works unless the code relies on things baked into the container image that are not easily reachable from your laptop.
Solution: Maximize Dockerfile cache
If you must build Docker images, structuring the Dockerfile to maximize cache can shrink a 10‑minute build to about one minute. In production you usually combine many commands into a single RUN to reduce layers, but during development you want many layers so that rarely‑changed steps (like pulling dependencies) are cached and only the final, frequently‑changed steps are rebuilt.
Below is an example development‑focused Dockerfile (simplified for illustration):
FROM golang:1.13-alpine as builder
RUN apk add busybox-static
WORKDIR /go/src/github.com/kelda-inc/blimp
ADD ./go.mod ./go.mod
ADD ./go.sum ./go.sum
ADD ./pkg ./pkg
ARG COMPILE_FLAGS
RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./pkg/...
ADD ./login-proxy ./login-proxy
RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./login-proxy/...
ADD ./registry ./registry
RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./registry/...
ADD ./sandbox ./sandbox
RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./sandbox/...
ADD ./cluster-controller ./cluster-controller
RUN CGO_ENABLED=0 go install -i -ldflags "${COMPILE_FLAGS}" ./cluster-controller/...
RUN mkdir /gobin
RUN cp /go/bin/cluster-controller /gobin/blimp-cluster-controller
RUN cp /go/bin/syncthing /gobin/blimp-syncthing
RUN cp /go/bin/init /gobin/blimp-init
RUN cp /go/bin/sbctl /gobin/blimp-sbctl
RUN cp /go/bin/registry /gobin/blimp-auth
RUN cp /go/bin/vcp /gobin/blimp-vcp
RUN cp /go/bin/login-proxy /gobin/login-proxy
FROM alpine
COPY --from=builder /bin/busybox.static /bin/busybox.static
COPY --from=builder /gobin/* /bin/Solution: Use host volumes
The best choice is often to mount a host volume that mirrors your code into the container, giving native‑speed execution while still running inside a container that provides runtime dependencies.
A host volume maps a directory on your laptop into a running container, automatically syncing changes made in your editor to the container.
Most languages have a tool that watches for file changes and restarts the process (e.g., nodemon for JavaScript). See the nodemon page for details.
Solution: Relax strong consistency on host volumes
By default Docker enforces strong consistency for file‑system mounts, which adds overhead on macOS and Windows because Docker runs inside a VM. In development you can safely relax this by adding the cached option to the volume definition:
volumes:
- "./app:/usr/src/app/app:cached"Solution: Code synchronization instead of host volumes
Another approach is to use a sync tool (similar to rsync ) that notifies the container of changes and copies files, avoiding the performance penalty of host mounts. Docker’s upcoming version includes Mutagen for this purpose; you can also use the open‑source Mutagen project.
Solution: Do not mount node_modules
For Node.js projects, exclude node_modules from the host volume and use a separate clean volume for it. This prevents slow host‑mounted node_modules during npm install :
volumes:
- ".:/usr/src/app"
- "/usr/src/app/node_modules"Then run npm install in the container entrypoint to populate the clean node_modules volume:
entrypoint:
- "sh"
- "-c"
- "npm install && ./node_modules/.bin/nodemon server.js"Error 2: Slow host‑volume performance
On macOS and Windows, file I/O through host volumes is notoriously slow because Docker runs inside a VM and the file system must be translated, similar to a network file system.
Solution: Use cached volumes (as shown above) to relax consistency and gain speed.
Error 3: Fragile configuration
Docker Compose files tend to grow via copy‑and‑paste, making them hard to maintain. Using an .env file separates environment variables from the main compose file, keeping secrets out of git history and allowing per‑developer settings.
Alternatively, use override files or the extends keyword (Compose v2) to layer configuration changes, or employ YAML anchors for similar effects in Compose v3.
Error 4: Fragile boot order
Services often fail to start because dependencies are not ready. Use depends_on to control startup order; in Compose v2 you can combine it with health checks, or use a script like wait‑for‑it.sh for custom waiting logic.
Error 5: Poor resource management
Docker Desktop can consume a lot of RAM and CPU, especially on macOS/Windows. Allocate sufficient resources (e.g., 8 GB RAM, 4 CPUs) and periodically run docker system prune to clean up unused images, containers, volumes, and networks.
When a laptop cannot handle the workload, consider running Docker Compose in the cloud using services like Blimp.
Final recommendations
Minimize container rebuilds by maximizing Dockerfile cache.
Use host volumes (with cached when appropriate) to run code at native speed.
Treat your Compose file as maintainable code: use .env , overrides, and modular composition.
Make boot reliable with depends_on and health checks.
Manage resources carefully: adjust Docker Desktop settings and prune unused artifacts.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.